Archived
Merge VS Code 1.21 source code (#1067)
* Initial VS Code 1.21 file copy with patches * A few more merges * Post npm install * Fix batch of build breaks * Fix more build breaks * Fix more build errors * Fix more build breaks * Runtime fixes 1 * Get connection dialog working with some todos * Fix a few packaging issues * Copy several node_modules to package build to fix loader issues * Fix breaks from master * A few more fixes * Make tests pass * First pass of license header updates * Second pass of license header updates * Fix restore dialog issues * Remove add additional themes menu items * fix select box issues where the list doesn't show up * formatting * Fix editor dispose issue * Copy over node modules to correct location on all platforms
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { IAction, IActionRunner, Action } from 'vs/base/common/actions';
|
||||
import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
|
||||
export interface IEvent {
|
||||
shiftKey?: boolean;
|
||||
ctrlKey?: boolean;
|
||||
altKey?: boolean;
|
||||
metaKey?: boolean;
|
||||
}
|
||||
|
||||
export class ContextSubMenu extends Action {
|
||||
constructor(label: string, public entries: (ContextSubMenu | IAction)[]) {
|
||||
super('contextsubmenu', label, '', true);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IContextMenuDelegate {
|
||||
getAnchor(): HTMLElement | { x: number; y: number; };
|
||||
getActions(): TPromise<(IAction | ContextSubMenu)[]>;
|
||||
getActionItem?(action: IAction): IActionItem;
|
||||
getActionsContext?(event?: IEvent): any;
|
||||
getKeyBinding?(action: IAction): ResolvedKeybinding;
|
||||
getMenuClassName?(): string;
|
||||
onHide?(didCancel: boolean): void;
|
||||
actionRunner?: IActionRunner;
|
||||
autoSelectFirstItem?: boolean;
|
||||
}
|
||||
@@ -39,4 +39,28 @@ export class DelayedDragHandler {
|
||||
public dispose(): void {
|
||||
this.clearDragTimeout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Common data transfers
|
||||
export const DataTransfers = {
|
||||
|
||||
/**
|
||||
* Application specific resource transfer type
|
||||
*/
|
||||
RESOURCES: 'ResourceURLs',
|
||||
|
||||
/**
|
||||
* Browser specific transfer type to download
|
||||
*/
|
||||
DOWNLOAD_URL: 'DownloadURL',
|
||||
|
||||
/**
|
||||
* Browser specific transfer type for files
|
||||
*/
|
||||
FILES: 'Files',
|
||||
|
||||
/**
|
||||
* Typicaly transfer type for copy/paste transfers.
|
||||
*/
|
||||
TEXT: 'text/plain'
|
||||
};
|
||||
|
||||
+31
-42
@@ -251,41 +251,26 @@ export function addDisposableNonBubblingMouseOutListener(node: Element, handler:
|
||||
});
|
||||
}
|
||||
|
||||
const _animationFrame = (function () {
|
||||
let emulatedRequestAnimationFrame = (callback: (time: number) => void): number => {
|
||||
return setTimeout(() => callback(new Date().getTime()), 0);
|
||||
};
|
||||
let nativeRequestAnimationFrame: (callback: (time: number) => void) => number =
|
||||
self.requestAnimationFrame
|
||||
|| (<any>self).msRequestAnimationFrame
|
||||
|| (<any>self).webkitRequestAnimationFrame
|
||||
|| (<any>self).mozRequestAnimationFrame
|
||||
|| (<any>self).oRequestAnimationFrame;
|
||||
|
||||
|
||||
|
||||
let emulatedCancelAnimationFrame = (id: number) => { };
|
||||
let nativeCancelAnimationFrame: (id: number) => void =
|
||||
self.cancelAnimationFrame || (<any>self).cancelRequestAnimationFrame
|
||||
|| (<any>self).msCancelAnimationFrame || (<any>self).msCancelRequestAnimationFrame
|
||||
|| (<any>self).webkitCancelAnimationFrame || (<any>self).webkitCancelRequestAnimationFrame
|
||||
|| (<any>self).mozCancelAnimationFrame || (<any>self).mozCancelRequestAnimationFrame
|
||||
|| (<any>self).oCancelAnimationFrame || (<any>self).oCancelRequestAnimationFrame;
|
||||
|
||||
let isNative = !!nativeRequestAnimationFrame;
|
||||
let request = nativeRequestAnimationFrame || emulatedRequestAnimationFrame;
|
||||
let cancel = nativeCancelAnimationFrame || emulatedCancelAnimationFrame;
|
||||
|
||||
return {
|
||||
isNative: isNative,
|
||||
request: (callback: (time: number) => void): number => {
|
||||
return request(callback);
|
||||
},
|
||||
cancel: (id: number) => {
|
||||
return cancel(id);
|
||||
}
|
||||
};
|
||||
})();
|
||||
interface IRequestAnimationFrame {
|
||||
(callback: (time: number) => void): number;
|
||||
}
|
||||
let _animationFrame: IRequestAnimationFrame = null;
|
||||
function doRequestAnimationFrame(callback: (time: number) => void): number {
|
||||
if (!_animationFrame) {
|
||||
const emulatedRequestAnimationFrame = (callback: (time: number) => void): number => {
|
||||
return setTimeout(() => callback(new Date().getTime()), 0);
|
||||
};
|
||||
_animationFrame = (
|
||||
self.requestAnimationFrame
|
||||
|| (<any>self).msRequestAnimationFrame
|
||||
|| (<any>self).webkitRequestAnimationFrame
|
||||
|| (<any>self).mozRequestAnimationFrame
|
||||
|| (<any>self).oRequestAnimationFrame
|
||||
|| emulatedRequestAnimationFrame
|
||||
);
|
||||
}
|
||||
return _animationFrame(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a callback to be run at the next animation frame.
|
||||
@@ -375,7 +360,7 @@ class AnimationFrameQueueItem implements IDisposable {
|
||||
|
||||
if (!animFrameRequested) {
|
||||
animFrameRequested = true;
|
||||
_animationFrame.request(animationFrameRunner);
|
||||
doRequestAnimationFrame(animationFrameRunner);
|
||||
}
|
||||
|
||||
return item;
|
||||
@@ -515,8 +500,6 @@ const sizeUtils = {
|
||||
|
||||
|
||||
__commaSentinel: false
|
||||
|
||||
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
@@ -688,7 +671,13 @@ export function createStyleSheet(container: HTMLElement = document.getElementsBy
|
||||
return style;
|
||||
}
|
||||
|
||||
const sharedStyle = <any>createStyleSheet();
|
||||
let _sharedStyleSheet: HTMLStyleElement = null;
|
||||
function getSharedStyleSheet(): HTMLStyleElement {
|
||||
if (!_sharedStyleSheet) {
|
||||
_sharedStyleSheet = createStyleSheet();
|
||||
}
|
||||
return _sharedStyleSheet;
|
||||
}
|
||||
|
||||
function getDynamicStyleSheetRules(style: any) {
|
||||
if (style && style.sheet && style.sheet.rules) {
|
||||
@@ -702,7 +691,7 @@ function getDynamicStyleSheetRules(style: any) {
|
||||
return [];
|
||||
}
|
||||
|
||||
export function createCSSRule(selector: string, cssText: string, style: HTMLStyleElement = sharedStyle): void {
|
||||
export function createCSSRule(selector: string, cssText: string, style: HTMLStyleElement = getSharedStyleSheet()): void {
|
||||
if (!style || !cssText) {
|
||||
return;
|
||||
}
|
||||
@@ -710,7 +699,7 @@ export function createCSSRule(selector: string, cssText: string, style: HTMLStyl
|
||||
(<CSSStyleSheet>style.sheet).insertRule(selector + '{' + cssText + '}', 0);
|
||||
}
|
||||
|
||||
export function removeCSSRulesContainingSelector(ruleName: string, style = sharedStyle): void {
|
||||
export function removeCSSRulesContainingSelector(ruleName: string, style: HTMLStyleElement = getSharedStyleSheet()): void {
|
||||
if (!style) {
|
||||
return;
|
||||
}
|
||||
@@ -725,7 +714,7 @@ export function removeCSSRulesContainingSelector(ruleName: string, style = share
|
||||
}
|
||||
|
||||
for (let i = toDelete.length - 1; i >= 0; i--) {
|
||||
style.sheet.deleteRule(toDelete[i]);
|
||||
(<any>style.sheet).deleteRule(toDelete[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -126,7 +126,12 @@ export const domEvent: IDomEvent = (element: EventHandler, type: string, useCapt
|
||||
return emitter.event;
|
||||
};
|
||||
|
||||
export function stop<T extends Event>(event: _Event<T>): _Event<T> {
|
||||
export interface CancellableEvent {
|
||||
preventDefault();
|
||||
stopPropagation();
|
||||
}
|
||||
|
||||
export function stop<T extends CancellableEvent>(event: _Event<T>): _Event<T> {
|
||||
return mapEvent(event, e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -8,16 +8,22 @@
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { defaultGenerator } from 'vs/base/common/idGenerator';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { removeMarkdownEscapes, IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { marked } from 'vs/base/common/marked/marked';
|
||||
import { marked, MarkedOptions } from 'vs/base/common/marked/marked';
|
||||
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface IContentActionHandler {
|
||||
callback: (content: string, event?: IMouseEvent) => void;
|
||||
disposeables: IDisposable[];
|
||||
}
|
||||
|
||||
export interface RenderOptions {
|
||||
className?: string;
|
||||
inline?: boolean;
|
||||
actionCallback?: (content: string, event?: IMouseEvent) => void;
|
||||
codeBlockRenderer?: (modeId: string, value: string) => string | TPromise<string>;
|
||||
actionHandler?: IContentActionHandler;
|
||||
codeBlockRenderer?: (modeId: string, value: string) => Thenable<string>;
|
||||
codeBlockRenderCallback?: () => void;
|
||||
}
|
||||
|
||||
function createElement(options: RenderOptions): HTMLElement {
|
||||
@@ -37,15 +43,12 @@ export function renderText(text: string, options: RenderOptions = {}): HTMLEleme
|
||||
|
||||
export function renderFormattedText(formattedText: string, options: RenderOptions = {}): HTMLElement {
|
||||
const element = createElement(options);
|
||||
_renderFormattedText(element, parseFormattedText(formattedText), options.actionCallback);
|
||||
_renderFormattedText(element, parseFormattedText(formattedText), options.actionHandler);
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create html nodes for the given content element.
|
||||
*
|
||||
* @param content a html element description
|
||||
* @param actionCallback a callback function for any action links in the string. Argument is the zero-based index of the clicked action.
|
||||
*/
|
||||
export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions = {}): HTMLElement {
|
||||
const element = createElement(options);
|
||||
@@ -53,7 +56,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions
|
||||
// signal to code-block render that the
|
||||
// element has been created
|
||||
let signalInnerHTML: Function;
|
||||
const withInnerHTML = new TPromise(c => signalInnerHTML = c);
|
||||
const withInnerHTML = new Promise(c => signalInnerHTML = c);
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
renderer.image = (href: string, title: string, text: string) => {
|
||||
@@ -108,7 +111,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions
|
||||
return text;
|
||||
|
||||
} else {
|
||||
return `<a href="#" data-href="${href}" title="${title || text}">${text}</a>`;
|
||||
return `<a href="#" data-href="${href}" title="${title || href}">${text}</a>`;
|
||||
}
|
||||
};
|
||||
renderer.paragraph = (text): string => {
|
||||
@@ -118,32 +121,43 @@ export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions
|
||||
if (options.codeBlockRenderer) {
|
||||
renderer.code = (code, lang) => {
|
||||
const value = options.codeBlockRenderer(lang, code);
|
||||
if (typeof value === 'string') {
|
||||
return value;
|
||||
}
|
||||
// when code-block rendering is async we return sync
|
||||
// but update the node with the real result later.
|
||||
const id = defaultGenerator.nextId();
|
||||
|
||||
if (TPromise.is(value)) {
|
||||
// when code-block rendering is async we return sync
|
||||
// but update the node with the real result later.
|
||||
const id = defaultGenerator.nextId();
|
||||
TPromise.join([value, withInnerHTML]).done(values => {
|
||||
const strValue = values[0] as string;
|
||||
// {{SQL CARBON EDIT}} - Promise.all not returning the strValue properly in original code?
|
||||
const promise = value.then(strValue => {
|
||||
withInnerHTML.then(e => {
|
||||
const span = element.querySelector(`div[data-code="${id}"]`);
|
||||
if (span) {
|
||||
span.innerHTML = strValue;
|
||||
}
|
||||
}, err => {
|
||||
}).catch(err => {
|
||||
// ignore
|
||||
});
|
||||
return `<div class="code" data-code="${id}">${escape(code)}</div>`;
|
||||
});
|
||||
|
||||
// original VS Code source
|
||||
// const promise = Promise.all([value, withInnerHTML]).then(values => {
|
||||
// const strValue = values[0];
|
||||
// const span = element.querySelector(`div[data-code="${id}"]`);
|
||||
// if (span) {
|
||||
// span.innerHTML = strValue;
|
||||
// }
|
||||
// }).catch(err => {
|
||||
// // ignore
|
||||
// });
|
||||
|
||||
if (options.codeBlockRenderCallback) {
|
||||
promise.then(options.codeBlockRenderCallback);
|
||||
}
|
||||
|
||||
return code;
|
||||
return `<div class="code" data-code="${id}">${escape(code)}</div>`;
|
||||
};
|
||||
}
|
||||
|
||||
if (options.actionCallback) {
|
||||
DOM.addStandardDisposableListener(element, 'click', event => {
|
||||
if (options.actionHandler) {
|
||||
options.actionHandler.disposeables.push(DOM.addStandardDisposableListener(element, 'click', event => {
|
||||
let target = event.target;
|
||||
if (target.tagName !== 'A') {
|
||||
target = target.parentElement;
|
||||
@@ -154,15 +168,17 @@ export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions
|
||||
|
||||
const href = target.dataset['href'];
|
||||
if (href) {
|
||||
options.actionCallback(href, event);
|
||||
options.actionHandler.callback(href, event);
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
element.innerHTML = marked(markdown.value, {
|
||||
const markedOptions: MarkedOptions = {
|
||||
sanitize: true,
|
||||
renderer
|
||||
});
|
||||
};
|
||||
|
||||
element.innerHTML = marked(markdown.value, markedOptions);
|
||||
signalInnerHTML();
|
||||
|
||||
return element;
|
||||
@@ -216,7 +232,7 @@ interface IFormatParseTree {
|
||||
children?: IFormatParseTree[];
|
||||
}
|
||||
|
||||
function _renderFormattedText(element: Node, treeNode: IFormatParseTree, actionCallback?: (content: string, event?: IMouseEvent) => void) {
|
||||
function _renderFormattedText(element: Node, treeNode: IFormatParseTree, actionHandler?: IContentActionHandler) {
|
||||
let child: Node;
|
||||
|
||||
if (treeNode.type === FormatType.Text) {
|
||||
@@ -228,12 +244,12 @@ function _renderFormattedText(element: Node, treeNode: IFormatParseTree, actionC
|
||||
else if (treeNode.type === FormatType.Italics) {
|
||||
child = document.createElement('i');
|
||||
}
|
||||
else if (treeNode.type === FormatType.Action) {
|
||||
else if (treeNode.type === FormatType.Action && actionHandler) {
|
||||
const a = document.createElement('a');
|
||||
a.href = '#';
|
||||
DOM.addStandardDisposableListener(a, 'click', (event) => {
|
||||
actionCallback(String(treeNode.index), event);
|
||||
});
|
||||
actionHandler.disposeables.push(DOM.addStandardDisposableListener(a, 'click', (event) => {
|
||||
actionHandler.callback(String(treeNode.index), event);
|
||||
}));
|
||||
|
||||
child = a;
|
||||
}
|
||||
@@ -250,7 +266,7 @@ function _renderFormattedText(element: Node, treeNode: IFormatParseTree, actionC
|
||||
|
||||
if (Array.isArray(treeNode.children)) {
|
||||
treeNode.children.forEach((nodeChild) => {
|
||||
_renderFormattedText(child, nodeChild, actionCallback);
|
||||
_renderFormattedText(child, nodeChild, actionHandler);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ export class Gesture implements IDisposable {
|
||||
|
||||
@memoize
|
||||
private static isTouchDevice(): boolean {
|
||||
return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0;
|
||||
return 'ontouchstart' in window as any || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
|
||||
@@ -17,6 +17,7 @@ import types = require('vs/base/common/types');
|
||||
import { EventType, Gesture } from 'vs/base/browser/touch';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export interface IActionItem {
|
||||
@@ -138,7 +139,7 @@ export class BaseActionItem implements IActionItem {
|
||||
if (this.options && this.options.isMenu) {
|
||||
this.onClick(e);
|
||||
} else {
|
||||
setTimeout(() => this.onClick(e), 50);
|
||||
setImmediate(() => this.onClick(e));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -755,9 +756,10 @@ export class SelectActionItem extends BaseActionItem {
|
||||
protected selectBox: SelectBox;
|
||||
protected toDispose: lifecycle.IDisposable[];
|
||||
|
||||
constructor(ctx: any, action: IAction, options: string[], selected: number) {
|
||||
constructor(ctx: any, action: IAction, options: string[], selected: number, contextViewProvider: IContextViewProvider
|
||||
) {
|
||||
super(ctx, action);
|
||||
this.selectBox = new SelectBox(options, selected);
|
||||
this.selectBox = new SelectBox(options, selected, contextViewProvider);
|
||||
|
||||
this.toDispose = [];
|
||||
this.toDispose.push(this.selectBox);
|
||||
|
||||
@@ -20,11 +20,4 @@
|
||||
.monaco-button.disabled {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Theming support */
|
||||
|
||||
.vs .monaco-text-button:focus,
|
||||
.vs-dark .monaco-text-button:focus {
|
||||
outline-color: rgba(255, 255, 255, .5); /* buttons have a blue color, so focus indication needs to be different */
|
||||
}
|
||||
@@ -13,8 +13,10 @@ 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';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface IButtonOptions extends IButtonStyles {
|
||||
title?: boolean;
|
||||
}
|
||||
|
||||
export interface IButtonStyles {
|
||||
@@ -44,6 +46,8 @@ export class Button {
|
||||
private _onDidClick = new Emitter<any>();
|
||||
readonly onDidClick: Event<any> = this._onDidClick.event;
|
||||
|
||||
private focusTracker: DOM.IFocusTracker;
|
||||
|
||||
constructor(container: Builder, options?: IButtonOptions);
|
||||
constructor(container: HTMLElement, options?: IButtonOptions);
|
||||
constructor(container: any, options?: IButtonOptions) {
|
||||
@@ -60,7 +64,7 @@ export class Button {
|
||||
'role': 'button'
|
||||
}).appendTo(container);
|
||||
|
||||
this.$el.on(DOM.EventType.CLICK, (e) => {
|
||||
this.$el.on(DOM.EventType.CLICK, e => {
|
||||
if (!this.enabled) {
|
||||
DOM.EventHelper.stop(e);
|
||||
return;
|
||||
@@ -69,7 +73,7 @@ export class Button {
|
||||
this._onDidClick.fire(e);
|
||||
});
|
||||
|
||||
this.$el.on(DOM.EventType.KEY_DOWN, (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)) {
|
||||
@@ -85,22 +89,31 @@ export class Button {
|
||||
}
|
||||
});
|
||||
|
||||
this.$el.on(DOM.EventType.MOUSE_OVER, (e) => {
|
||||
this.$el.on(DOM.EventType.MOUSE_OVER, e => {
|
||||
if (!this.$el.hasClass('disabled')) {
|
||||
const hoverBackground = this.buttonHoverBackground ? this.buttonHoverBackground.toString() : null;
|
||||
if (hoverBackground) {
|
||||
this.$el.style('background-color', hoverBackground);
|
||||
}
|
||||
this.setHoverBackground();
|
||||
}
|
||||
});
|
||||
|
||||
this.$el.on(DOM.EventType.MOUSE_OUT, (e) => {
|
||||
this.$el.on(DOM.EventType.MOUSE_OUT, e => {
|
||||
this.applyStyles(); // restore standard styles
|
||||
});
|
||||
|
||||
// Also set hover background when button is focused for feedback
|
||||
this.focusTracker = DOM.trackFocus(this.$el.getHTMLElement());
|
||||
this.focusTracker.onDidFocus(() => this.setHoverBackground());
|
||||
this.focusTracker.onDidBlur(() => this.applyStyles()); // restore standard styles
|
||||
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
private setHoverBackground(): void {
|
||||
const hoverBackground = this.buttonHoverBackground ? this.buttonHoverBackground.toString() : null;
|
||||
if (hoverBackground) {
|
||||
this.$el.style('background-color', hoverBackground);
|
||||
}
|
||||
}
|
||||
|
||||
style(styles: IButtonStyles): void {
|
||||
this.buttonForeground = styles.buttonForeground;
|
||||
this.buttonBackground = styles.buttonBackground;
|
||||
@@ -126,7 +139,7 @@ export class Button {
|
||||
}
|
||||
}
|
||||
|
||||
getElement(): HTMLElement {
|
||||
get element(): HTMLElement {
|
||||
return this.$el.getHTMLElement();
|
||||
}
|
||||
|
||||
@@ -135,6 +148,9 @@ export class Button {
|
||||
this.$el.addClass('monaco-text-button');
|
||||
}
|
||||
this.$el.text(value);
|
||||
if (this.options.title) {
|
||||
this.$el.title(value);
|
||||
}
|
||||
}
|
||||
|
||||
set icon(iconClassName: string) {
|
||||
@@ -167,8 +183,66 @@ export class Button {
|
||||
if (this.$el) {
|
||||
this.$el.dispose();
|
||||
this.$el = null;
|
||||
|
||||
this.focusTracker.dispose();
|
||||
this.focusTracker = null;
|
||||
}
|
||||
|
||||
this._onDidClick.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class ButtonGroup {
|
||||
private _buttons: Button[];
|
||||
private toDispose: IDisposable[];
|
||||
|
||||
constructor(container: Builder, count: number, options?: IButtonOptions);
|
||||
constructor(container: HTMLElement, count: number, options?: IButtonOptions);
|
||||
constructor(container: any, count: number, options?: IButtonOptions) {
|
||||
this._buttons = [];
|
||||
this.toDispose = [];
|
||||
|
||||
this.create(container, count, options);
|
||||
}
|
||||
|
||||
get buttons(): Button[] {
|
||||
return this._buttons;
|
||||
}
|
||||
|
||||
private create(container: Builder, count: number, options?: IButtonOptions): void;
|
||||
private create(container: HTMLElement, count: number, options?: IButtonOptions): void;
|
||||
private create(container: any, count: number, options?: IButtonOptions): void {
|
||||
for (let index = 0; index < count; index++) {
|
||||
const button = new Button(container, options);
|
||||
this._buttons.push(button);
|
||||
this.toDispose.push(button);
|
||||
|
||||
// Implement keyboard access in buttons if there are multiple
|
||||
if (count > 1) {
|
||||
$(button.element).on(DOM.EventType.KEY_DOWN, e => {
|
||||
const event = new StandardKeyboardEvent(e as KeyboardEvent);
|
||||
let eventHandled = true;
|
||||
|
||||
// Next / Previous Button
|
||||
let buttonIndexToFocus: number;
|
||||
if (event.equals(KeyCode.LeftArrow)) {
|
||||
buttonIndexToFocus = index > 0 ? index - 1 : this._buttons.length - 1;
|
||||
} else if (event.equals(KeyCode.RightArrow)) {
|
||||
buttonIndexToFocus = index === this._buttons.length - 1 ? 0 : index + 1;
|
||||
} else {
|
||||
eventHandled = false;
|
||||
}
|
||||
|
||||
if (eventHandled) {
|
||||
this._buttons[buttonIndexToFocus].focus();
|
||||
DOM.EventHelper.stop(e, true);
|
||||
}
|
||||
}, this.toDispose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.custom-checkbox {
|
||||
.monaco-custom-checkbox {
|
||||
margin-left: 2px;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
@@ -28,15 +28,15 @@
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.custom-checkbox:hover,
|
||||
.custom-checkbox.checked {
|
||||
.monaco-custom-checkbox:hover,
|
||||
.monaco-custom-checkbox.checked {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.hc-black .custom-checkbox {
|
||||
.hc-black .monaco-custom-checkbox {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.hc-black .custom-checkbox:hover {
|
||||
.hc-black .monaco-custom-checkbox:hover {
|
||||
background: none;
|
||||
}
|
||||
@@ -45,7 +45,7 @@ export class Checkbox extends Widget {
|
||||
|
||||
this.domNode = document.createElement('div');
|
||||
this.domNode.title = this._opts.title;
|
||||
this.domNode.className = 'custom-checkbox ' + this._opts.actionClassName + ' ' + (this._checked ? 'checked' : 'unchecked');
|
||||
this.domNode.className = 'monaco-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));
|
||||
|
||||
@@ -128,6 +128,7 @@ export class ContextView {
|
||||
|
||||
public setContainer(container: HTMLElement): void {
|
||||
if (this.$container) {
|
||||
this.$container.getHTMLElement().removeChild(this.$view.getHTMLElement());
|
||||
this.$container.off(ContextView.BUBBLE_UP_EVENTS);
|
||||
this.$container.off(ContextView.BUBBLE_DOWN_EVENTS, true);
|
||||
this.$container = null;
|
||||
@@ -230,7 +231,7 @@ export class ContextView {
|
||||
this.$view.removeClass('top', 'bottom', 'left', 'right');
|
||||
this.$view.addClass(anchorPosition === AnchorPosition.BELOW ? 'bottom' : 'top');
|
||||
this.$view.addClass(anchorAlignment === AnchorAlignment.LEFT ? 'left' : 'right');
|
||||
this.$view.style({ top: result.top + 'px', left: result.left + 1 + 'px', width: 'initial' });
|
||||
this.$view.style({ top: result.top + 'px', left: result.left + 'px', width: 'initial' });
|
||||
}
|
||||
|
||||
public hide(data?: any): void {
|
||||
|
||||
@@ -29,8 +29,4 @@
|
||||
|
||||
.dropdown > .dropdown-action, .dropdown > .dropdown-action > .action-label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dropdown > .dropdown-label:not(:empty) {
|
||||
padding: 0 .5em;
|
||||
}
|
||||
@@ -9,13 +9,14 @@ import 'vs/css!./dropdown';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
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 { ActionRunner, IAction, IActionRunner } from 'vs/base/common/actions';
|
||||
import { BaseActionItem, IActionItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
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';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { EventHelper, EventType } from 'vs/base/browser/dom';
|
||||
import { IContextMenuDelegate } from 'vs/base/browser/contextmenu';
|
||||
|
||||
export interface ILabelRenderer {
|
||||
(container: HTMLElement): IDisposable;
|
||||
@@ -53,12 +54,11 @@ export class BaseDropdown extends ActionRunner {
|
||||
this.$label.on([EventType.CLICK, EventType.MOUSE_DOWN, GestureEventType.Tap], (e: Event) => {
|
||||
EventHelper.stop(e, true); // prevent default click behaviour to trigger
|
||||
}).on([EventType.MOUSE_DOWN, GestureEventType.Tap], (e: Event) => {
|
||||
// We want to show the context menu on dropdown so that as a user you can press and hold the
|
||||
// mouse button, make a choice of action in the menu and release the mouse to trigger that
|
||||
// action.
|
||||
// Due to some weird bugs though, we delay showing the menu to unwind event stack
|
||||
// (see https://github.com/Microsoft/vscode/issues/27648)
|
||||
setTimeout(() => this.show(), 100);
|
||||
if (e instanceof MouseEvent && e.detail > 1) {
|
||||
return; // prevent multiple clicks to open multiple context menus (https://github.com/Microsoft/vscode/issues/41363)
|
||||
}
|
||||
|
||||
this.show();
|
||||
}).appendTo(this.$el);
|
||||
|
||||
let cleanupFn = labelRenderer(this.$label.getHTMLElement());
|
||||
@@ -165,16 +165,6 @@ export class Dropdown extends BaseDropdown {
|
||||
}
|
||||
}
|
||||
|
||||
export interface IContextMenuDelegate {
|
||||
getAnchor(): HTMLElement | { x: number; y: number; };
|
||||
getActions(): TPromise<IAction[]>;
|
||||
getActionItem?(action: IAction): IActionItem;
|
||||
getActionsContext?(): any;
|
||||
getKeyBinding?(action: IAction): ResolvedKeybinding;
|
||||
getMenuClassName?(): string;
|
||||
onHide?(didCancel: boolean): void;
|
||||
}
|
||||
|
||||
export interface IContextMenuProvider {
|
||||
showContextMenu(delegate: IContextMenuDelegate): void;
|
||||
}
|
||||
@@ -236,11 +226,91 @@ export class DropdownMenu extends BaseDropdown {
|
||||
getActionItem: (action) => this.menuOptions && this.menuOptions.actionItemProvider ? this.menuOptions.actionItemProvider(action) : null,
|
||||
getKeyBinding: (action: IAction) => this.menuOptions && this.menuOptions.getKeyBinding ? this.menuOptions.getKeyBinding(action) : null,
|
||||
getMenuClassName: () => this.menuClassName,
|
||||
onHide: () => this.element.removeClass('active')
|
||||
onHide: () => this.element.removeClass('active'),
|
||||
actionRunner: this.menuOptions ? this.menuOptions.actionRunner : null
|
||||
});
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
export class DropdownMenuActionItem extends BaseActionItem {
|
||||
private menuActionsOrProvider: any;
|
||||
private dropdownMenu: DropdownMenu;
|
||||
private contextMenuProvider: IContextMenuProvider;
|
||||
private actionItemProvider: IActionItemProvider;
|
||||
private keybindings: (action: IAction) => ResolvedKeybinding;
|
||||
private clazz: string;
|
||||
|
||||
constructor(action: IAction, menuActions: IAction[], contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, actionRunner: IActionRunner, keybindings: (action: IAction) => ResolvedKeybinding, clazz: string);
|
||||
constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, actionRunner: IActionRunner, keybindings: (action: IAction) => ResolvedKeybinding, clazz: string);
|
||||
constructor(action: IAction, menuActionsOrProvider: any, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, actionRunner: IActionRunner, keybindings: (action: IAction) => ResolvedKeybinding, clazz: string) {
|
||||
super(null, action);
|
||||
|
||||
this.menuActionsOrProvider = menuActionsOrProvider;
|
||||
this.contextMenuProvider = contextMenuProvider;
|
||||
this.actionItemProvider = actionItemProvider;
|
||||
this.actionRunner = actionRunner;
|
||||
this.keybindings = keybindings;
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
let labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable => {
|
||||
this.builder = $('a.action-label').attr({
|
||||
tabIndex: '0',
|
||||
role: 'button',
|
||||
'aria-haspopup': 'true',
|
||||
title: this._action.label || '',
|
||||
class: this.clazz
|
||||
});
|
||||
|
||||
this.builder.appendTo(el);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
let options: IDropdownMenuOptions = {
|
||||
contextMenuProvider: this.contextMenuProvider,
|
||||
labelRenderer: labelRenderer
|
||||
};
|
||||
|
||||
// Render the DropdownMenu around a simple action to toggle it
|
||||
if (Array.isArray(this.menuActionsOrProvider)) {
|
||||
options.actions = this.menuActionsOrProvider;
|
||||
} else {
|
||||
options.actionProvider = this.menuActionsOrProvider;
|
||||
}
|
||||
|
||||
this.dropdownMenu = new DropdownMenu(container, options);
|
||||
|
||||
this.dropdownMenu.menuOptions = {
|
||||
actionItemProvider: this.actionItemProvider,
|
||||
actionRunner: this.actionRunner,
|
||||
getKeyBinding: this.keybindings,
|
||||
context: this._context
|
||||
};
|
||||
}
|
||||
|
||||
public setActionContext(newContext: any): void {
|
||||
super.setActionContext(newContext);
|
||||
|
||||
if (this.dropdownMenu) {
|
||||
this.dropdownMenu.menuOptions.context = newContext;
|
||||
}
|
||||
}
|
||||
|
||||
public show(): void {
|
||||
if (this.dropdownMenu) {
|
||||
this.dropdownMenu.show();
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.dropdownMenu.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -3,29 +3,29 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.vs .custom-checkbox.monaco-case-sensitive {
|
||||
.vs .monaco-custom-checkbox.monaco-case-sensitive {
|
||||
background: url('case-sensitive.svg') center center no-repeat;
|
||||
}
|
||||
.hc-black .custom-checkbox.monaco-case-sensitive,
|
||||
.hc-black .custom-checkbox.monaco-case-sensitive:hover,
|
||||
.vs-dark .custom-checkbox.monaco-case-sensitive {
|
||||
.hc-black .monaco-custom-checkbox.monaco-case-sensitive,
|
||||
.hc-black .monaco-custom-checkbox.monaco-case-sensitive:hover,
|
||||
.vs-dark .monaco-custom-checkbox.monaco-case-sensitive {
|
||||
background: url('case-sensitive-dark.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs .custom-checkbox.monaco-whole-word {
|
||||
.vs .monaco-custom-checkbox.monaco-whole-word {
|
||||
background: url('whole-word.svg') center center no-repeat;
|
||||
}
|
||||
.hc-black .custom-checkbox.monaco-whole-word,
|
||||
.hc-black .custom-checkbox.monaco-whole-word:hover,
|
||||
.vs-dark .custom-checkbox.monaco-whole-word {
|
||||
.hc-black .monaco-custom-checkbox.monaco-whole-word,
|
||||
.hc-black .monaco-custom-checkbox.monaco-whole-word:hover,
|
||||
.vs-dark .monaco-custom-checkbox.monaco-whole-word {
|
||||
background: url('whole-word-dark.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs .custom-checkbox.monaco-regex {
|
||||
.vs .monaco-custom-checkbox.monaco-regex {
|
||||
background: url('regex.svg') center center no-repeat;
|
||||
}
|
||||
.hc-black .custom-checkbox.monaco-regex,
|
||||
.hc-black .custom-checkbox.monaco-regex:hover,
|
||||
.vs-dark .custom-checkbox.monaco-regex {
|
||||
.hc-black .monaco-custom-checkbox.monaco-regex,
|
||||
.hc-black .monaco-custom-checkbox.monaco-regex:hover,
|
||||
.vs-dark .monaco-custom-checkbox.monaco-regex {
|
||||
background: url('regex-dark.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
@@ -16,13 +16,16 @@ import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface IIconLabelCreationOptions {
|
||||
supportHighlights?: boolean;
|
||||
supportDescriptionHighlights?: boolean;
|
||||
}
|
||||
|
||||
export interface IIconLabelOptions {
|
||||
export interface IIconLabelValueOptions {
|
||||
title?: string;
|
||||
descriptionTitle?: string;
|
||||
extraClasses?: string[];
|
||||
italic?: boolean;
|
||||
matches?: IMatch[];
|
||||
descriptionMatches?: IMatch[];
|
||||
}
|
||||
|
||||
class FastLabelNode {
|
||||
@@ -63,7 +66,11 @@ class FastLabelNode {
|
||||
}
|
||||
|
||||
this._title = title;
|
||||
this._element.title = title;
|
||||
if (this._title) {
|
||||
this._element.title = title;
|
||||
} else {
|
||||
this._element.removeAttribute('title');
|
||||
}
|
||||
}
|
||||
|
||||
public set empty(empty: boolean) {
|
||||
@@ -82,21 +89,27 @@ class FastLabelNode {
|
||||
|
||||
export class IconLabel {
|
||||
private domNode: FastLabelNode;
|
||||
private labelDescriptionContainer: FastLabelNode;
|
||||
private labelNode: FastLabelNode | HighlightedLabel;
|
||||
private descriptionNode: FastLabelNode;
|
||||
private descriptionNode: FastLabelNode | HighlightedLabel;
|
||||
private descriptionNodeFactory: () => FastLabelNode | HighlightedLabel;
|
||||
|
||||
constructor(container: HTMLElement, options?: IIconLabelCreationOptions) {
|
||||
this.domNode = new FastLabelNode(dom.append(container, dom.$('.monaco-icon-label')));
|
||||
|
||||
const labelDescriptionContainer = new FastLabelNode(dom.append(this.domNode.element, dom.$('.monaco-icon-label-description-container')));
|
||||
this.labelDescriptionContainer = new FastLabelNode(dom.append(this.domNode.element, dom.$('.monaco-icon-label-description-container')));
|
||||
|
||||
if (options && options.supportHighlights) {
|
||||
this.labelNode = new HighlightedLabel(dom.append(labelDescriptionContainer.element, dom.$('a.label-name')));
|
||||
this.labelNode = new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')));
|
||||
} else {
|
||||
this.labelNode = new FastLabelNode(dom.append(labelDescriptionContainer.element, dom.$('a.label-name')));
|
||||
this.labelNode = new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')));
|
||||
}
|
||||
|
||||
this.descriptionNode = new FastLabelNode(dom.append(labelDescriptionContainer.element, dom.$('span.label-description')));
|
||||
if (options && options.supportDescriptionHighlights) {
|
||||
this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')));
|
||||
} else {
|
||||
this.descriptionNodeFactory = () => new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')));
|
||||
}
|
||||
}
|
||||
|
||||
public get element(): HTMLElement {
|
||||
@@ -105,21 +118,11 @@ export class IconLabel {
|
||||
|
||||
public onClick(callback: (event: MouseEvent) => void): IDisposable {
|
||||
return combinedDisposable([
|
||||
dom.addDisposableListener(this.labelElement, dom.EventType.CLICK, (e: MouseEvent) => callback(e)),
|
||||
dom.addDisposableListener(this.descriptionNode.element, dom.EventType.CLICK, (e: MouseEvent) => callback(e))
|
||||
dom.addDisposableListener(this.labelDescriptionContainer.element, dom.EventType.CLICK, (e: MouseEvent) => callback(e)),
|
||||
]);
|
||||
}
|
||||
|
||||
private get labelElement(): HTMLElement {
|
||||
const labelNode = this.labelNode;
|
||||
if (labelNode instanceof HighlightedLabel) {
|
||||
return labelNode.element;
|
||||
}
|
||||
|
||||
return labelNode.element;
|
||||
}
|
||||
|
||||
public setValue(label?: string, description?: string, options?: IIconLabelOptions): void {
|
||||
public setValue(label?: string, description?: string, options?: IIconLabelValueOptions): void {
|
||||
const classes = ['monaco-icon-label'];
|
||||
if (options) {
|
||||
if (options.extraClasses) {
|
||||
@@ -134,21 +137,39 @@ export class IconLabel {
|
||||
this.domNode.className = classes.join(' ');
|
||||
this.domNode.title = options && options.title ? options.title : '';
|
||||
|
||||
const labelNode = this.labelNode;
|
||||
if (labelNode instanceof HighlightedLabel) {
|
||||
labelNode.set(label || '', options ? options.matches : void 0);
|
||||
if (this.labelNode instanceof HighlightedLabel) {
|
||||
this.labelNode.set(label || '', options ? options.matches : void 0);
|
||||
} else {
|
||||
labelNode.textContent = label || '';
|
||||
this.labelNode.textContent = label || '';
|
||||
}
|
||||
|
||||
this.descriptionNode.textContent = description || '';
|
||||
this.descriptionNode.empty = !description;
|
||||
if (description || this.descriptionNode) {
|
||||
if (!this.descriptionNode) {
|
||||
this.descriptionNode = this.descriptionNodeFactory(); // description node is created lazily on demand
|
||||
}
|
||||
|
||||
if (this.descriptionNode instanceof HighlightedLabel) {
|
||||
this.descriptionNode.set(description || '', options ? options.descriptionMatches : void 0);
|
||||
if (options && options.descriptionTitle) {
|
||||
this.descriptionNode.element.title = options.descriptionTitle;
|
||||
} else {
|
||||
this.descriptionNode.element.removeAttribute('title');
|
||||
}
|
||||
} else {
|
||||
this.descriptionNode.textContent = description || '';
|
||||
this.descriptionNode.title = options && options.descriptionTitle ? options.descriptionTitle : '';
|
||||
this.descriptionNode.empty = !description;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.domNode.dispose();
|
||||
this.labelNode.dispose();
|
||||
this.descriptionNode.dispose();
|
||||
|
||||
if (this.descriptionNode) {
|
||||
this.descriptionNode.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,8 +63,8 @@
|
||||
/* make sure selection color wins when a label is being selected */
|
||||
.monaco-tree.focused .selected .monaco-icon-label, /* tree */
|
||||
.monaco-tree.focused .selected .monaco-icon-label::after,
|
||||
.monaco-list:focus .focused.selected .monaco-icon-label, /* list */
|
||||
.monaco-list:focus .focused.selected .monaco-icon-label::after
|
||||
.monaco-list:focus .selected .monaco-icon-label, /* list */
|
||||
.monaco-list:focus .selected .monaco-icon-label::after
|
||||
{
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
line-height: 17px;
|
||||
min-height: 34px;
|
||||
margin-top: -1px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Action bar support */
|
||||
|
||||
@@ -29,7 +29,7 @@ export interface IInputOptions extends IInputBoxStyles {
|
||||
flexibleHeight?: boolean;
|
||||
actions?: IAction[];
|
||||
|
||||
// {{SQL CARBON EDIT}} Canidate for addition to vscode
|
||||
// {{SQL CARBON EDIT}} Candidate for addition to vscode
|
||||
min?: string;
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ export class InputBox extends Widget {
|
||||
|
||||
if (this.options.validationOptions) {
|
||||
this.validation = this.options.validationOptions.validation;
|
||||
// {{SQL CARBON EDIT}} Canidate for addition to vscode
|
||||
// {{SQL CARBON EDIT}} Candidate for addition to vscode
|
||||
this.showValidationMessage = true;
|
||||
}
|
||||
|
||||
@@ -173,8 +173,7 @@ export class InputBox extends Widget {
|
||||
}
|
||||
|
||||
if (this.placeholder) {
|
||||
this.input.setAttribute('placeholder', this.placeholder);
|
||||
this.input.title = this.placeholder;
|
||||
this.setPlaceHolder(this.placeholder);
|
||||
}
|
||||
|
||||
this.oninput(this.input, () => this.onValueChange());
|
||||
@@ -201,7 +200,7 @@ export class InputBox extends Widget {
|
||||
if (this.options.actions) {
|
||||
this.actionbar = this._register(new ActionBar(this.element));
|
||||
this.actionbar.push(this.options.actions, { icon: true, label: false });
|
||||
// {{SQL CARBON EDIT}} Canidate for addition to vscode
|
||||
// {{SQL CARBON EDIT}} Candidate for addition to vscode
|
||||
this.input.style.paddingRight = (this.options.actions.length * 22) + 'px';
|
||||
}
|
||||
|
||||
@@ -219,6 +218,7 @@ export class InputBox extends Widget {
|
||||
public setPlaceHolder(placeHolder: string): void {
|
||||
if (this.input) {
|
||||
this.input.setAttribute('placeholder', placeHolder);
|
||||
this.input.title = placeHolder;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,7 +378,7 @@ export class InputBox extends Widget {
|
||||
}
|
||||
|
||||
private _showMessage(): void {
|
||||
// {{SQL CARBON EDIT}} Canidate for addition to vscode
|
||||
// {{SQL CARBON EDIT}} Candidate for addition to vscode
|
||||
if (!this.contextViewProvider || !this.message || !this.showValidationMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-list {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -17,6 +17,12 @@ export interface IRenderer<TElement, TTemplateData> {
|
||||
disposeTemplate(templateData: TTemplateData): void;
|
||||
}
|
||||
|
||||
export interface IListOpenEvent<T> {
|
||||
elements: T[];
|
||||
indexes: number[];
|
||||
browserEvent?: UIEvent;
|
||||
}
|
||||
|
||||
export interface IListEvent<T> {
|
||||
elements: T[];
|
||||
indexes: number[];
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
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, IListStyles } from './listWidget';
|
||||
import { IDelegate, IRenderer, IListEvent, IListOpenEvent } from './list';
|
||||
import { List, IListStyles, IListOptions } from './listWidget';
|
||||
import { IPagedModel } from 'vs/base/common/paging';
|
||||
import Event, { mapEvent } from 'vs/base/common/event';
|
||||
|
||||
@@ -67,7 +67,7 @@ export class PagedList<T> {
|
||||
container: HTMLElement,
|
||||
delegate: IDelegate<number>,
|
||||
renderers: IPagedRenderer<T, any>[],
|
||||
options: IListOptions<any> = {} // TODO@Joao: should be IListOptions<T>
|
||||
options: IListOptions<any> = {}
|
||||
) {
|
||||
const pagedRenderers = renderers.map(r => new PagedRenderer<T, ITemplateData<T>>(r, () => this.model));
|
||||
this.list = new List(container, delegate, pagedRenderers, options);
|
||||
@@ -97,6 +97,10 @@ export class PagedList<T> {
|
||||
return mapEvent(this.list.onFocusChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
|
||||
}
|
||||
|
||||
get onOpen(): Event<IListOpenEvent<T>> {
|
||||
return mapEvent(this.list.onOpen, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
|
||||
}
|
||||
|
||||
get onSelectionChange(): Event<IListEvent<T>> {
|
||||
return mapEvent(this.list.onSelectionChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
|
||||
}
|
||||
@@ -126,8 +130,8 @@ export class PagedList<T> {
|
||||
this.list.scrollTop = scrollTop;
|
||||
}
|
||||
|
||||
open(indexes: number[]): void {
|
||||
this.list.open(indexes);
|
||||
open(indexes: number[], browserEvent?: UIEvent): void {
|
||||
this.list.open(indexes, browserEvent);
|
||||
}
|
||||
|
||||
setFocus(indexes: number[]): void {
|
||||
@@ -166,6 +170,10 @@ export class PagedList<T> {
|
||||
this.list.setSelection(indexes);
|
||||
}
|
||||
|
||||
getSelection(): number[] {
|
||||
return this.list.getSelection();
|
||||
}
|
||||
|
||||
layout(height?: number): void {
|
||||
this.list.layout(height);
|
||||
}
|
||||
|
||||
@@ -52,10 +52,12 @@ interface IItem<T> {
|
||||
|
||||
export interface IListViewOptions {
|
||||
useShadows?: boolean;
|
||||
verticalScrollMode?: ScrollbarVisibility;
|
||||
}
|
||||
|
||||
const DefaultOptions: IListViewOptions = {
|
||||
useShadows: true
|
||||
useShadows: true,
|
||||
verticalScrollMode: ScrollbarVisibility.Auto
|
||||
};
|
||||
|
||||
export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
@@ -106,18 +108,23 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
this.scrollableElement = new ScrollableElement(this.rowsContainer, {
|
||||
alwaysConsumeMouseWheel: true,
|
||||
horizontal: ScrollbarVisibility.Hidden,
|
||||
vertical: ScrollbarVisibility.Auto,
|
||||
vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode),
|
||||
useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows)
|
||||
});
|
||||
|
||||
this._domNode.appendChild(this.scrollableElement.getDomNode());
|
||||
container.appendChild(this._domNode);
|
||||
|
||||
this.disposables = [this.rangeMap, this.gesture, this.scrollableElement];
|
||||
this.disposables = [this.rangeMap, this.gesture, this.scrollableElement, this.cache];
|
||||
|
||||
this.scrollableElement.onScroll(this.onScroll, this, this.disposables);
|
||||
domEvent(this.rowsContainer, TouchEventType.Change)(this.onTouchChange, this, this.disposables);
|
||||
|
||||
// Prevent the monaco-scrollable-element from scrolling
|
||||
// https://github.com/Microsoft/vscode/issues/44181
|
||||
domEvent(this.scrollableElement.getDomNode(), 'scroll')
|
||||
(e => (e.target as HTMLElement).scrollTop = 0, null, this.disposables);
|
||||
|
||||
const onDragOver = mapEvent(domEvent(this.rowsContainer, 'dragover'), e => new DragMouseEvent(e));
|
||||
onDragOver(this.onDragOver, this, this.disposables);
|
||||
|
||||
@@ -148,7 +155,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
const removeRange = intersect(previousRenderRange, deleteRange);
|
||||
|
||||
for (let i = removeRange.start; i < removeRange.end; i++) {
|
||||
this.removeItemFromDOM(this.items[i]);
|
||||
this.removeItemFromDOM(i);
|
||||
}
|
||||
|
||||
const previousRestRange: IRange = { start: start + deleteCount, end: this.items.length };
|
||||
@@ -181,19 +188,20 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
const removeRange = removeRanges[r];
|
||||
|
||||
for (let i = removeRange.start; i < removeRange.end; i++) {
|
||||
this.removeItemFromDOM(this.items[i]);
|
||||
this.removeItemFromDOM(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));
|
||||
const beforeElement = this.getNextToLastElement(insertRanges);
|
||||
|
||||
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);
|
||||
this.insertItemInDOM(i, beforeElement);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,16 +260,17 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
const rangesToInsert = relativeComplement(renderRange, previousRenderRange);
|
||||
const rangesToRemove = relativeComplement(previousRenderRange, renderRange);
|
||||
const beforeElement = this.getNextToLastElement(rangesToInsert);
|
||||
|
||||
for (const range of rangesToInsert) {
|
||||
for (let i = range.start; i < range.end; i++) {
|
||||
this.insertItemInDOM(this.items[i], i);
|
||||
this.insertItemInDOM(i, beforeElement);
|
||||
}
|
||||
}
|
||||
|
||||
for (const range of rangesToRemove) {
|
||||
for (let i = range.start; i < range.end; i++) {
|
||||
this.removeItemFromDOM(this.items[i], );
|
||||
this.removeItemFromDOM(i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,28 +288,38 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
// DOM operations
|
||||
|
||||
private insertItemInDOM(item: IItem<T>, index: number): void {
|
||||
private insertItemInDOM(index: number, beforeElement: HTMLElement | null): void {
|
||||
const item = this.items[index];
|
||||
|
||||
if (!item.row) {
|
||||
item.row = this.cache.alloc(item.templateId);
|
||||
}
|
||||
|
||||
if (!item.row.domNode.parentElement) {
|
||||
this.rowsContainer.appendChild(item.row.domNode);
|
||||
if (beforeElement) {
|
||||
this.rowsContainer.insertBefore(item.row.domNode, beforeElement);
|
||||
} else {
|
||||
this.rowsContainer.appendChild(item.row.domNode);
|
||||
}
|
||||
}
|
||||
|
||||
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}`);
|
||||
this.updateItemInDOM(item, index);
|
||||
|
||||
const renderer = this.renderers.get(item.templateId);
|
||||
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}`);
|
||||
item.row.domNode.setAttribute('data-last-element', index === this.length - 1 ? 'true' : 'false');
|
||||
item.row.domNode.setAttribute('aria-setsize', `${this.length}`);
|
||||
item.row.domNode.setAttribute('aria-posinset', `${index + 1}`);
|
||||
}
|
||||
|
||||
private removeItemFromDOM(item: IItem<T>): void {
|
||||
private removeItemFromDOM(index: number): void {
|
||||
const item = this.items[index];
|
||||
this.cache.release(item.row);
|
||||
item.row = null;
|
||||
}
|
||||
@@ -448,6 +467,26 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
};
|
||||
}
|
||||
|
||||
private getNextToLastElement(ranges: IRange[]): HTMLElement | null {
|
||||
const lastRange = ranges[ranges.length - 1];
|
||||
|
||||
if (!lastRange) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nextToLastItem = this.items[lastRange.end];
|
||||
|
||||
if (!nextToLastItem) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!nextToLastItem.row) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return nextToLastItem.row.domNode;
|
||||
}
|
||||
|
||||
// Dispose
|
||||
|
||||
dispose() {
|
||||
|
||||
@@ -15,11 +15,13 @@ import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import Event, { Emitter, EventBufferer, chain, mapEvent, anyEvent } from 'vs/base/common/event';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { IDelegate, IRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent } from './list';
|
||||
import { IDelegate, IRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IListOpenEvent } from './list';
|
||||
import { ListView, IListViewOptions } from './listView';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { clamp } from 'vs/base/common/numbers';
|
||||
|
||||
export interface IIdentityProvider<T> {
|
||||
(element: T): string;
|
||||
@@ -202,32 +204,6 @@ class FocusTrait<T> extends Trait<T> {
|
||||
}
|
||||
}
|
||||
|
||||
class Aria<T> implements IRenderer<T, HTMLElement>, ISpliceable<T> {
|
||||
|
||||
private length = 0;
|
||||
|
||||
get templateId(): string {
|
||||
return 'aria';
|
||||
}
|
||||
|
||||
splice(start: number, deleteCount: number, elements: T[]): void {
|
||||
this.length += elements.length - deleteCount;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): HTMLElement {
|
||||
return container;
|
||||
}
|
||||
|
||||
renderElement(element: T, index: number, container: HTMLElement): void {
|
||||
container.setAttribute('aria-setsize', `${this.length}`);
|
||||
container.setAttribute('aria-posinset', `${index + 1}`);
|
||||
}
|
||||
|
||||
disposeTemplate(container: HTMLElement): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The TraitSpliceable is used as a util class to be able
|
||||
* to preserve traits across splice calls, given an identity
|
||||
@@ -260,6 +236,7 @@ function isInputElement(e: HTMLElement): boolean {
|
||||
class KeyboardController<T> implements IDisposable {
|
||||
|
||||
private disposables: IDisposable[];
|
||||
private openController: IOpenController;
|
||||
|
||||
constructor(
|
||||
private list: List<T>,
|
||||
@@ -269,6 +246,8 @@ class KeyboardController<T> implements IDisposable {
|
||||
const multipleSelectionSupport = !(options.multipleSelectionSupport === false);
|
||||
this.disposables = [];
|
||||
|
||||
this.openController = options.openController || DefaultOpenController;
|
||||
|
||||
const onKeyDown = chain(domEvent(view.domNode, 'keydown'))
|
||||
.filter(e => !isInputElement(e.target as HTMLElement))
|
||||
.map(e => new StandardKeyboardEvent(e));
|
||||
@@ -289,7 +268,10 @@ class KeyboardController<T> implements IDisposable {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.list.setSelection(this.list.getFocus());
|
||||
this.list.open(this.list.getFocus());
|
||||
|
||||
if (this.openController.shouldOpen(e.browserEvent)) {
|
||||
this.list.open(this.list.getFocus(), e.browserEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private onUpArrow(e: StandardKeyboardEvent): void {
|
||||
@@ -343,21 +325,84 @@ class KeyboardController<T> implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
function isSelectionSingleChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
class DOMFocusController<T> implements IDisposable {
|
||||
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private list: List<T>,
|
||||
private view: ListView<T>
|
||||
) {
|
||||
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.Tab && !e.ctrlKey && !e.metaKey && !e.shiftKey && !e.altKey)
|
||||
.on(this.onTab, this, this.disposables);
|
||||
}
|
||||
|
||||
private onTab(e: StandardKeyboardEvent): void {
|
||||
if (e.target !== this.view.domNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const focus = this.list.getFocus();
|
||||
|
||||
if (focus.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const focusedDomElement = this.view.domElement(focus[0]);
|
||||
const tabIndexElement = focusedDomElement.querySelector('[tabIndex]');
|
||||
|
||||
if (!tabIndexElement || !(tabIndexElement instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
tabIndexElement.focus();
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
export function isSelectionSingleChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
return platform.isMacintosh ? event.browserEvent.metaKey : event.browserEvent.ctrlKey;
|
||||
}
|
||||
|
||||
function isSelectionRangeChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
export function isSelectionRangeChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
return event.browserEvent.shiftKey;
|
||||
}
|
||||
|
||||
function isSelectionChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
return isSelectionSingleChangeEvent(event) || isSelectionRangeChangeEvent(event);
|
||||
function isMouseRightClick(event: UIEvent): boolean {
|
||||
return event instanceof MouseEvent && event.button === 2;
|
||||
}
|
||||
|
||||
const DefaultMultipleSelectionContoller = {
|
||||
isSelectionSingleChangeEvent,
|
||||
isSelectionRangeChangeEvent
|
||||
};
|
||||
|
||||
const DefaultOpenController = {
|
||||
shouldOpen: (event: UIEvent) => {
|
||||
if (event instanceof MouseEvent) {
|
||||
return !isMouseRightClick(event);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} as IOpenController;
|
||||
|
||||
class MouseController<T> implements IDisposable {
|
||||
|
||||
private multipleSelectionSupport: boolean;
|
||||
private multipleSelectionController: IMultipleSelectionController<T>;
|
||||
private openController: IOpenController;
|
||||
private didJustPressContextMenuKey: boolean = false;
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
@@ -397,7 +442,13 @@ class MouseController<T> implements IDisposable {
|
||||
private view: ListView<T>,
|
||||
private options: IListOptions<T> = {}
|
||||
) {
|
||||
this.multipleSelectionSupport = options.multipleSelectionSupport !== false;
|
||||
this.multipleSelectionSupport = !(options.multipleSelectionSupport === false);
|
||||
|
||||
if (this.multipleSelectionSupport) {
|
||||
this.multipleSelectionController = options.multipleSelectionController || DefaultMultipleSelectionContoller;
|
||||
}
|
||||
|
||||
this.openController = options.openController || DefaultOpenController;
|
||||
|
||||
view.onMouseDown(this.onMouseDown, this, this.disposables);
|
||||
view.onMouseClick(this.onPointer, this, this.disposables);
|
||||
@@ -407,6 +458,26 @@ class MouseController<T> implements IDisposable {
|
||||
Gesture.addTarget(view.domNode);
|
||||
}
|
||||
|
||||
private isSelectionSingleChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
if (this.multipleSelectionController) {
|
||||
return this.multipleSelectionController.isSelectionSingleChangeEvent(event);
|
||||
}
|
||||
|
||||
return platform.isMacintosh ? event.browserEvent.metaKey : event.browserEvent.ctrlKey;
|
||||
}
|
||||
|
||||
private isSelectionRangeChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
if (this.multipleSelectionController) {
|
||||
return this.multipleSelectionController.isSelectionRangeChangeEvent(event);
|
||||
}
|
||||
|
||||
return event.browserEvent.shiftKey;
|
||||
}
|
||||
|
||||
private isSelectionChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
return this.isSelectionSingleChangeEvent(event) || this.isSelectionRangeChangeEvent(event);
|
||||
}
|
||||
|
||||
private onMouseDown(e: IListMouseEvent<T> | IListTouchEvent<T>): void {
|
||||
if (this.options.focusOnMouseDown === false) {
|
||||
e.browserEvent.preventDefault();
|
||||
@@ -416,39 +487,48 @@ class MouseController<T> implements IDisposable {
|
||||
}
|
||||
|
||||
let reference = this.list.getFocus()[0];
|
||||
reference = reference === undefined ? this.list.getSelection()[0] : reference;
|
||||
const selection = this.list.getSelection();
|
||||
reference = reference === undefined ? selection[0] : reference;
|
||||
|
||||
if (this.multipleSelectionSupport && isSelectionRangeChangeEvent(e)) {
|
||||
if (this.multipleSelectionSupport && this.isSelectionRangeChangeEvent(e)) {
|
||||
return this.changeSelection(e, reference);
|
||||
}
|
||||
|
||||
const focus = e.index;
|
||||
this.list.setFocus([focus]);
|
||||
if (selection.every(s => s !== focus)) {
|
||||
this.list.setFocus([focus]);
|
||||
}
|
||||
|
||||
if (this.multipleSelectionSupport && isSelectionChangeEvent(e)) {
|
||||
if (this.multipleSelectionSupport && this.isSelectionChangeEvent(e)) {
|
||||
return this.changeSelection(e, reference);
|
||||
}
|
||||
|
||||
if (this.options.selectOnMouseDown) {
|
||||
if (this.options.selectOnMouseDown && !isMouseRightClick(e.browserEvent)) {
|
||||
this.list.setSelection([focus]);
|
||||
this.list.open([focus]);
|
||||
|
||||
if (this.openController.shouldOpen(e.browserEvent)) {
|
||||
this.list.open([focus], e.browserEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onPointer(e: IListMouseEvent<T>): void {
|
||||
if (this.multipleSelectionSupport && isSelectionChangeEvent(e)) {
|
||||
if (this.multipleSelectionSupport && this.isSelectionChangeEvent(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.options.selectOnMouseDown) {
|
||||
const focus = this.list.getFocus();
|
||||
this.list.setSelection(focus);
|
||||
this.list.open(focus);
|
||||
|
||||
if (this.openController.shouldOpen(e.browserEvent)) {
|
||||
this.list.open(focus, e.browserEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onDoubleClick(e: IListMouseEvent<T>): void {
|
||||
if (this.multipleSelectionSupport && isSelectionChangeEvent(e)) {
|
||||
if (this.multipleSelectionSupport && this.isSelectionChangeEvent(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -460,7 +540,7 @@ class MouseController<T> implements IDisposable {
|
||||
private changeSelection(e: IListMouseEvent<T> | IListTouchEvent<T>, reference: number | undefined): void {
|
||||
const focus = e.index;
|
||||
|
||||
if (isSelectionRangeChangeEvent(e) && reference !== undefined) {
|
||||
if (this.isSelectionRangeChangeEvent(e) && reference !== undefined) {
|
||||
const min = Math.min(reference, focus);
|
||||
const max = Math.max(reference, focus);
|
||||
const rangeSelection = range(min, max + 1);
|
||||
@@ -474,7 +554,7 @@ class MouseController<T> implements IDisposable {
|
||||
const newSelection = disjunction(rangeSelection, relativeComplement(selection, contiguousRange));
|
||||
this.list.setSelection(newSelection);
|
||||
|
||||
} else if (isSelectionSingleChangeEvent(e)) {
|
||||
} else if (this.isSelectionSingleChangeEvent(e)) {
|
||||
const selection = this.list.getSelection();
|
||||
const newSelection = selection.filter(i => i !== focus);
|
||||
|
||||
@@ -491,6 +571,15 @@ class MouseController<T> implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
export interface IMultipleSelectionController<T> {
|
||||
isSelectionSingleChangeEvent(event: IListMouseEvent<T> | IListTouchEvent<T>): boolean;
|
||||
isSelectionRangeChangeEvent(event: IListMouseEvent<T> | IListTouchEvent<T>): boolean;
|
||||
}
|
||||
|
||||
export interface IOpenController {
|
||||
shouldOpen(event: UIEvent): boolean;
|
||||
}
|
||||
|
||||
export interface IListOptions<T> extends IListViewOptions, IListStyles {
|
||||
identityProvider?: IIdentityProvider<T>;
|
||||
ariaLabel?: string;
|
||||
@@ -498,7 +587,10 @@ export interface IListOptions<T> extends IListViewOptions, IListStyles {
|
||||
selectOnMouseDown?: boolean;
|
||||
focusOnMouseDown?: boolean;
|
||||
keyboardSupport?: boolean;
|
||||
verticalScrollMode?: ScrollbarVisibility;
|
||||
multipleSelectionSupport?: boolean;
|
||||
multipleSelectionController?: IMultipleSelectionController<T>;
|
||||
openController?: IOpenController;
|
||||
}
|
||||
|
||||
export interface IListStyles {
|
||||
@@ -660,8 +752,9 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
private eventBufferer = new EventBufferer();
|
||||
private view: ListView<T>;
|
||||
private spliceable: ISpliceable<T>;
|
||||
private disposables: IDisposable[];
|
||||
protected disposables: IDisposable[];
|
||||
private styleElement: HTMLStyleElement;
|
||||
private mouseController: MouseController<T>;
|
||||
|
||||
@memoize get onFocusChange(): Event<IListEvent<T>> {
|
||||
return mapEvent(this.eventBufferer.wrapEvent(this.focus.onChange), e => this.toListEvent(e));
|
||||
@@ -673,9 +766,9 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
readonly onContextMenu: Event<IListContextMenuEvent<T>> = Event.None;
|
||||
|
||||
private _onOpen = new Emitter<number[]>();
|
||||
@memoize get onOpen(): Event<IListEvent<T>> {
|
||||
return mapEvent(this._onOpen.event, indexes => this.toListEvent({ indexes }));
|
||||
private _onOpen = new Emitter<IListOpenEvent<T>>();
|
||||
@memoize get onOpen(): Event<IListOpenEvent<T>> {
|
||||
return this._onOpen.event;
|
||||
}
|
||||
|
||||
private _onPin = new Emitter<number[]>();
|
||||
@@ -709,13 +802,12 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
renderers: IRenderer<T, any>[],
|
||||
options: IListOptions<T> = DefaultOptions
|
||||
) {
|
||||
const aria = new Aria();
|
||||
this.focus = new FocusTrait(i => this.getElementDomId(i));
|
||||
this.selection = new Trait('selected');
|
||||
|
||||
mixin(options, defaultStyles, false);
|
||||
|
||||
renderers = renderers.map(r => new PipelineRenderer(r.templateId, [aria, this.focus.renderer, this.selection.renderer, r]));
|
||||
renderers = renderers.map(r => new PipelineRenderer(r.templateId, [this.focus.renderer, this.selection.renderer, r]));
|
||||
|
||||
this.view = new ListView(container, delegate, renderers, options);
|
||||
this.view.domNode.setAttribute('role', 'tree');
|
||||
@@ -725,7 +817,6 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
this.styleElement = DOM.createStyleSheet(this.view.domNode);
|
||||
|
||||
this.spliceable = new CombinedSpliceable([
|
||||
aria,
|
||||
new TraitSpliceable(this.focus, this.view, options.identityProvider),
|
||||
new TraitSpliceable(this.selection, this.view, options.identityProvider),
|
||||
this.view
|
||||
@@ -736,15 +827,17 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
this.onDidFocus = mapEvent(domEvent(this.view.domNode, 'focus', true), () => null);
|
||||
this.onDidBlur = mapEvent(domEvent(this.view.domNode, 'blur', true), () => null);
|
||||
|
||||
this.disposables.push(new DOMFocusController(this, this.view));
|
||||
|
||||
if (typeof options.keyboardSupport !== 'boolean' || options.keyboardSupport) {
|
||||
const controller = new KeyboardController(this, this.view, options);
|
||||
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.mouseController = new MouseController(this, this.view, options);
|
||||
this.disposables.push(this.mouseController);
|
||||
this.onContextMenu = this.mouseController.onContextMenu;
|
||||
}
|
||||
|
||||
this.onFocusChange(this._onFocusChange, this, this.disposables);
|
||||
@@ -790,6 +883,12 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
|
||||
setSelection(indexes: number[]): void {
|
||||
for (const index of indexes) {
|
||||
if (index < 0 || index >= this.length) {
|
||||
throw new Error(`Invalid index ${index}`);
|
||||
}
|
||||
}
|
||||
|
||||
indexes = indexes.sort(numericSort);
|
||||
this.selection.set(indexes);
|
||||
}
|
||||
@@ -820,6 +919,12 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
|
||||
setFocus(indexes: number[]): void {
|
||||
for (const index of indexes) {
|
||||
if (index < 0 || index >= this.length) {
|
||||
throw new Error(`Invalid index ${index}`);
|
||||
}
|
||||
}
|
||||
|
||||
indexes = indexes.sort(numericSort);
|
||||
this.focus.set(indexes);
|
||||
}
|
||||
@@ -903,17 +1008,18 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
|
||||
reveal(index: number, relativeTop?: number): void {
|
||||
if (index < 0 || index >= this.length) {
|
||||
throw new Error(`Invalid index ${index}`);
|
||||
}
|
||||
|
||||
const scrollTop = this.view.getScrollTop();
|
||||
const elementTop = this.view.elementTop(index);
|
||||
const elementHeight = this.view.elementHeight(index);
|
||||
|
||||
if (isNumber(relativeTop)) {
|
||||
relativeTop = relativeTop < 0 ? 0 : relativeTop;
|
||||
relativeTop = relativeTop > 1 ? 1 : relativeTop;
|
||||
|
||||
// y = mx + b
|
||||
const m = elementHeight - this.view.renderHeight;
|
||||
this.view.setScrollTop(m * relativeTop + elementTop);
|
||||
this.view.setScrollTop(m * clamp(relativeTop, 0, 1) + elementTop);
|
||||
} else {
|
||||
const viewItemBottom = elementTop + elementHeight;
|
||||
const wrapperBottom = scrollTop + this.view.renderHeight;
|
||||
@@ -926,6 +1032,28 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the relative position of an element rendered in the list.
|
||||
* Returns `null` if the element isn't *entirely* in the visible viewport.
|
||||
*/
|
||||
getRelativeTop(index: number): number | null {
|
||||
if (index < 0 || index >= this.length) {
|
||||
throw new Error(`Invalid index ${index}`);
|
||||
}
|
||||
|
||||
const scrollTop = this.view.getScrollTop();
|
||||
const elementTop = this.view.elementTop(index);
|
||||
const elementHeight = this.view.elementHeight(index);
|
||||
|
||||
if (elementTop < scrollTop || elementTop + elementHeight > scrollTop + this.view.renderHeight) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// y = mx + b
|
||||
const m = elementHeight - this.view.renderHeight;
|
||||
return Math.abs((scrollTop - elementTop) / m);
|
||||
}
|
||||
|
||||
private getElementDomId(index: number): string {
|
||||
return `${this.idPrefix}_${index}`;
|
||||
}
|
||||
@@ -938,11 +1066,23 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
return this.view.domNode;
|
||||
}
|
||||
|
||||
open(indexes: number[]): void {
|
||||
this._onOpen.fire(indexes);
|
||||
open(indexes: number[], browserEvent?: UIEvent): void {
|
||||
for (const index of indexes) {
|
||||
if (index < 0 || index >= this.length) {
|
||||
throw new Error(`Invalid index ${index}`);
|
||||
}
|
||||
}
|
||||
|
||||
this._onOpen.fire({ indexes, elements: indexes.map(i => this.view.element(i)), browserEvent });
|
||||
}
|
||||
|
||||
pin(indexes: number[]): void {
|
||||
for (const index of indexes) {
|
||||
if (index < 0 || index >= this.length) {
|
||||
throw new Error(`Invalid index ${index}`);
|
||||
}
|
||||
}
|
||||
|
||||
this._onPin.fire(indexes);
|
||||
}
|
||||
|
||||
@@ -951,6 +1091,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
if (styles.listFocusBackground) {
|
||||
content.push(`.monaco-list.${this.idPrefix}:focus .monaco-list-row.focused { background-color: ${styles.listFocusBackground}; }`);
|
||||
content.push(`.monaco-list.${this.idPrefix}:focus .monaco-list-row.focused:hover { background-color: ${styles.listFocusBackground}; }`); // overwrite :hover style in this case!
|
||||
}
|
||||
|
||||
if (styles.listFocusForeground) {
|
||||
|
||||
@@ -82,7 +82,7 @@ export class RowCache<T> implements IDisposable {
|
||||
|
||||
this.cache.forEach((cachedRows, templateId) => {
|
||||
for (const cachedRow of cachedRows) {
|
||||
const renderer = this.renderers[templateId];
|
||||
const renderer = this.renderers.get(templateId);
|
||||
renderer.disposeTemplate(cachedRow.templateData);
|
||||
cachedRow.domNode = null;
|
||||
cachedRow.templateData = null;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"version": "3.1.0",
|
||||
"license": "MIT",
|
||||
"licenseDetail": [
|
||||
"The Source EULA (MIT)",
|
||||
"The Source EULA",
|
||||
"",
|
||||
"(c) 2012-2015 GitHub",
|
||||
"",
|
||||
|
||||
@@ -45,7 +45,9 @@ export class ProgressBar {
|
||||
private animationStopToken: ValueCallback;
|
||||
private progressBarBackground: Color;
|
||||
|
||||
constructor(builder: Builder, options?: IProgressBarOptions) {
|
||||
constructor(container: Builder, options?: IProgressBarOptions);
|
||||
constructor(container: HTMLElement, options?: IProgressBarOptions);
|
||||
constructor(container: any, options?: IProgressBarOptions) {
|
||||
this.options = options || Object.create(null);
|
||||
mixin(this.options, defaultOpts, false);
|
||||
|
||||
@@ -54,11 +56,13 @@ export class ProgressBar {
|
||||
|
||||
this.progressBarBackground = this.options.progressBarBackground;
|
||||
|
||||
this.create(builder);
|
||||
this.create(container);
|
||||
}
|
||||
|
||||
private create(parent: Builder): void {
|
||||
parent.div({ 'class': css_progress_container }, (builder) => {
|
||||
private create(container: Builder): void;
|
||||
private create(container: HTMLElement): void;
|
||||
private create(container: any): void {
|
||||
$(container).div({ 'class': css_progress_container }, (builder) => {
|
||||
this.element = builder.clone();
|
||||
|
||||
builder.div({ 'class': css_progress_bit }).on([DOM.EventType.ANIMATION_START, DOM.EventType.ANIMATION_END, DOM.EventType.ANIMATION_ITERATION], (e: Event) => {
|
||||
|
||||
@@ -1,243 +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 'vs/css!./resourceviewer';
|
||||
import nls = require('vs/nls');
|
||||
import mimes = require('vs/base/common/mime');
|
||||
import URI from 'vs/base/common/uri';
|
||||
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 { LRUCache } from 'vs/base/common/map';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
interface MapExtToMediaMimes {
|
||||
[index: string]: string;
|
||||
}
|
||||
|
||||
// Known media mimes that we can handle
|
||||
const mapExtToMediaMimes: MapExtToMediaMimes = {
|
||||
'.bmp': 'image/bmp',
|
||||
'.gif': 'image/gif',
|
||||
'.jpg': 'image/jpg',
|
||||
'.jpeg': 'image/jpg',
|
||||
'.jpe': 'image/jpg',
|
||||
'.png': 'image/png',
|
||||
'.tiff': 'image/tiff',
|
||||
'.tif': 'image/tiff',
|
||||
'.ico': 'image/x-icon',
|
||||
'.tga': 'image/x-tga',
|
||||
'.psd': 'image/vnd.adobe.photoshop',
|
||||
'.webp': 'image/webp',
|
||||
'.mid': 'audio/midi',
|
||||
'.midi': 'audio/midi',
|
||||
'.mp4a': 'audio/mp4',
|
||||
'.mpga': 'audio/mpeg',
|
||||
'.mp2': 'audio/mpeg',
|
||||
'.mp2a': 'audio/mpeg',
|
||||
'.mp3': 'audio/mpeg',
|
||||
'.m2a': 'audio/mpeg',
|
||||
'.m3a': 'audio/mpeg',
|
||||
'.oga': 'audio/ogg',
|
||||
'.ogg': 'audio/ogg',
|
||||
'.spx': 'audio/ogg',
|
||||
'.aac': 'audio/x-aac',
|
||||
'.wav': 'audio/x-wav',
|
||||
'.wma': 'audio/x-ms-wma',
|
||||
'.mp4': 'video/mp4',
|
||||
'.mp4v': 'video/mp4',
|
||||
'.mpg4': 'video/mp4',
|
||||
'.mpeg': 'video/mpeg',
|
||||
'.mpg': 'video/mpeg',
|
||||
'.mpe': 'video/mpeg',
|
||||
'.m1v': 'video/mpeg',
|
||||
'.m2v': 'video/mpeg',
|
||||
'.ogv': 'video/ogg',
|
||||
'.qt': 'video/quicktime',
|
||||
'.mov': 'video/quicktime',
|
||||
'.webm': 'video/webm',
|
||||
'.mkv': 'video/x-matroska',
|
||||
'.mk3d': 'video/x-matroska',
|
||||
'.mks': 'video/x-matroska',
|
||||
'.wmv': 'video/x-ms-wmv',
|
||||
'.flv': 'video/x-flv',
|
||||
'.avi': 'video/x-msvideo',
|
||||
'.movie': 'video/x-sgi-movie'
|
||||
};
|
||||
|
||||
export interface IResourceDescriptor {
|
||||
resource: URI;
|
||||
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 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);
|
||||
if (!cached) {
|
||||
cached = { etag: descriptor.etag, src };
|
||||
IMAGE_RESOURCE_ETAG_CACHE.set(src, cached);
|
||||
}
|
||||
|
||||
if (cached.etag !== descriptor.etag) {
|
||||
cached.etag = descriptor.etag;
|
||||
cached.src = `${src}?${Date.now()}`; // bypass cache with this trick
|
||||
}
|
||||
|
||||
return cached.src;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to actually render the given resource into the provided container. Will adjust scrollbar (if provided) automatically based on loading
|
||||
* progress of the binary resource.
|
||||
*/
|
||||
export class ResourceViewer {
|
||||
|
||||
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 readonly MAX_IMAGE_SIZE = ResourceViewer.MB; // showing images inline is memory intense, so we have a limit
|
||||
|
||||
public static show(
|
||||
descriptor: IResourceDescriptor,
|
||||
container: Builder,
|
||||
scrollbar: DomScrollableElement,
|
||||
openExternal: (uri: URI) => void,
|
||||
metadataClb?: (meta: string) => void
|
||||
): void {
|
||||
|
||||
// Ensure CSS class
|
||||
$(container).setClass('monaco-resource-viewer');
|
||||
|
||||
// Lookup media mime if any
|
||||
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 unless they are large
|
||||
if (mime.indexOf('image/') >= 0) {
|
||||
if (ResourceViewer.inlineImage(descriptor)) {
|
||||
$(container)
|
||||
.empty()
|
||||
.addClass('image')
|
||||
.img({ src: imageSrc(descriptor) })
|
||||
.on(DOM.EventType.LOAD, (e, img) => {
|
||||
const imgElement = <HTMLImageElement>img.getHTMLElement();
|
||||
if (imgElement.naturalWidth > imgElement.width || imgElement.naturalHeight > imgElement.height) {
|
||||
$(container).addClass('oversized');
|
||||
|
||||
img.on(DOM.EventType.CLICK, (e, img) => {
|
||||
$(container).toggleClass('full-size');
|
||||
|
||||
scrollbar.scanDomNode();
|
||||
});
|
||||
}
|
||||
|
||||
if (metadataClb) {
|
||||
metadataClb(nls.localize('imgMeta', "{0}x{1} {2}", imgElement.naturalWidth, imgElement.naturalHeight, ResourceViewer.formatSize(descriptor.size)));
|
||||
}
|
||||
|
||||
scrollbar.scanDomNode();
|
||||
});
|
||||
} else {
|
||||
const imageContainer = $(container)
|
||||
.empty()
|
||||
.p({
|
||||
text: nls.localize('largeImageError', "The image is too large to display in the editor. ")
|
||||
});
|
||||
|
||||
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);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle generic Binary Files
|
||||
else {
|
||||
$(container)
|
||||
.empty()
|
||||
.span({
|
||||
text: nls.localize('nativeBinaryError', "The file will not be displayed in the editor because it is either binary, very large or uses an unsupported text encoding.")
|
||||
});
|
||||
|
||||
if (metadataClb) {
|
||||
metadataClb(ResourceViewer.formatSize(descriptor.size));
|
||||
}
|
||||
|
||||
scrollbar.scanDomNode();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (size < ResourceViewer.MB) {
|
||||
return nls.localize('sizeKB', "{0}KB", (size / ResourceViewer.KB).toFixed(2));
|
||||
}
|
||||
|
||||
if (size < ResourceViewer.GB) {
|
||||
return nls.localize('sizeMB', "{0}MB", (size / ResourceViewer.MB).toFixed(2));
|
||||
}
|
||||
|
||||
if (size < ResourceViewer.TB) {
|
||||
return nls.localize('sizeGB', "{0}GB", (size / ResourceViewer.GB).toFixed(2));
|
||||
}
|
||||
|
||||
return nls.localize('sizeTB', "{0}TB", (size / ResourceViewer.TB).toFixed(2));
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ export interface ISashEvent {
|
||||
currentX: number;
|
||||
startY: number;
|
||||
currentY: number;
|
||||
altKey: boolean;
|
||||
}
|
||||
|
||||
export interface ISashOptions {
|
||||
@@ -140,12 +141,14 @@ export class Sash {
|
||||
let mouseDownEvent = new StandardMouseEvent(e);
|
||||
let startX = mouseDownEvent.posx;
|
||||
let startY = mouseDownEvent.posy;
|
||||
const altKey = mouseDownEvent.altKey;
|
||||
|
||||
let startEvent: ISashEvent = {
|
||||
startX: startX,
|
||||
currentX: startX,
|
||||
startY: startY,
|
||||
currentY: startY
|
||||
currentY: startY,
|
||||
altKey
|
||||
};
|
||||
|
||||
this.$e.addClass('active');
|
||||
@@ -162,7 +165,8 @@ export class Sash {
|
||||
startX: startX,
|
||||
currentX: mouseMoveEvent.posx,
|
||||
startY: startY,
|
||||
currentY: mouseMoveEvent.posy
|
||||
currentY: mouseMoveEvent.posy,
|
||||
altKey
|
||||
};
|
||||
|
||||
this._onDidChange.fire(event);
|
||||
@@ -190,12 +194,15 @@ export class Sash {
|
||||
|
||||
let startX = event.pageX;
|
||||
let startY = event.pageY;
|
||||
const altKey = event.altKey;
|
||||
|
||||
|
||||
this._onDidStart.fire({
|
||||
startX: startX,
|
||||
currentX: startX,
|
||||
startY: startY,
|
||||
currentY: startY
|
||||
currentY: startY,
|
||||
altKey
|
||||
});
|
||||
|
||||
listeners.push(DOM.addDisposableListener(this.$e.getHTMLElement(), EventType.Change, (event: GestureEvent) => {
|
||||
@@ -204,7 +211,8 @@ export class Sash {
|
||||
startX: startX,
|
||||
currentX: event.pageX,
|
||||
startY: startY,
|
||||
currentY: event.pageY
|
||||
currentY: event.pageY,
|
||||
altKey
|
||||
});
|
||||
}
|
||||
}));
|
||||
@@ -368,4 +376,4 @@ export class VSash extends Disposable implements IVerticalSashLayoutProvider {
|
||||
this.sash.layout();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
}
|
||||
.monaco-scrollable-element > .invisible {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.monaco-scrollable-element > .invisible.fade {
|
||||
-webkit-transition: opacity 800ms linear;
|
||||
|
||||
@@ -330,7 +330,7 @@ export abstract class AbstractScrollableElement extends Widget {
|
||||
|
||||
// Convert vertical scrolling to horizontal if shift is held, this
|
||||
// is handled at a higher level on Mac
|
||||
const shiftConvert = !Platform.isMacintosh && e.browserEvent.shiftKey;
|
||||
const shiftConvert = !Platform.isMacintosh && e.browserEvent && e.browserEvent.shiftKey;
|
||||
if ((this._options.scrollYToX || shiftConvert) && !deltaX) {
|
||||
deltaX = deltaY;
|
||||
deltaY = 0;
|
||||
|
||||
@@ -7,17 +7,39 @@ import 'vs/css!./selectBox';
|
||||
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
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 { deepClone } from 'vs/base/common/objects';
|
||||
import { deepClone, mixin } from 'vs/base/common/objects';
|
||||
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { SelectBoxNative } from 'vs/base/browser/ui/selectBox/selectBoxNative';
|
||||
import { SelectBoxList } from 'vs/base/browser/ui/selectBox/selectBoxCustom';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
|
||||
export interface ISelectBoxStyles {
|
||||
// Public SelectBox interface - Calls routed to appropriate select implementation class
|
||||
|
||||
export interface ISelectBoxDelegate {
|
||||
|
||||
// Public SelectBox Interface
|
||||
readonly onDidSelect: Event<ISelectData>;
|
||||
setOptions(options: string[], selected?: number, disabled?: number): void;
|
||||
select(index: number): void;
|
||||
focus(): void;
|
||||
blur(): void;
|
||||
dispose(): void;
|
||||
|
||||
// Delegated Widget interface
|
||||
render(container: HTMLElement): void;
|
||||
style(styles: ISelectBoxStyles): void;
|
||||
applyStyles(): void;
|
||||
}
|
||||
|
||||
export interface ISelectBoxStyles extends IListStyles {
|
||||
selectBackground?: Color;
|
||||
selectListBackground?: Color;
|
||||
selectForeground?: Color;
|
||||
selectBorder?: Color;
|
||||
focusBorder?: Color;
|
||||
}
|
||||
|
||||
export const defaultStyles = {
|
||||
@@ -31,114 +53,75 @@ export interface ISelectData {
|
||||
index: number;
|
||||
}
|
||||
|
||||
export class SelectBox extends Widget {
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected selectElement: HTMLSelectElement;
|
||||
export class SelectBox extends Widget implements ISelectBoxDelegate {
|
||||
protected options: string[];
|
||||
private selected: number;
|
||||
private _onDidSelect: Emitter<ISelectData>;
|
||||
private toDispose: IDisposable[];
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected selectElement: HTMLSelectElement;
|
||||
protected selectBackground: Color;
|
||||
protected selectForeground: Color;
|
||||
protected selectBorder: Color;
|
||||
private toDispose: IDisposable[];
|
||||
|
||||
constructor(options: string[], selected: number, styles: ISelectBoxStyles = deepClone(defaultStyles)) {
|
||||
private styles: ISelectBoxStyles;
|
||||
private selectBoxDelegate: ISelectBoxDelegate;
|
||||
|
||||
constructor(options: string[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles = deepClone(defaultStyles)) {
|
||||
super();
|
||||
|
||||
this.selectElement = document.createElement('select');
|
||||
this.selectElement.className = 'select-box';
|
||||
|
||||
this.setOptions(options, selected);
|
||||
this.toDispose = [];
|
||||
this._onDidSelect = new Emitter<ISelectData>();
|
||||
|
||||
this.selectBackground = styles.selectBackground;
|
||||
this.selectForeground = styles.selectForeground;
|
||||
this.selectBorder = styles.selectBorder;
|
||||
mixin(this.styles, defaultStyles, false);
|
||||
|
||||
this.toDispose.push(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => {
|
||||
this.selectElement.title = e.target.value;
|
||||
this._onDidSelect.fire({
|
||||
index: e.target.selectedIndex,
|
||||
selected: e.target.value
|
||||
});
|
||||
}));
|
||||
this.toDispose.push(dom.addStandardDisposableListener(this.selectElement, 'keydown', (e) => {
|
||||
if (e.equals(KeyCode.Space) || e.equals(KeyCode.Enter)) {
|
||||
// Space is used to expand select box, do not propagate it (prevent action bar action run)
|
||||
e.stopPropagation();
|
||||
}
|
||||
}));
|
||||
// Instantiate select implementation based on platform
|
||||
if (isMacintosh) {
|
||||
this.selectBoxDelegate = new SelectBoxNative(options, selected, styles);
|
||||
} else {
|
||||
this.selectBoxDelegate = new SelectBoxList(options, selected, contextViewProvider, styles);
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
this.selectElement = (<any>this.selectBoxDelegate).selectElement;
|
||||
|
||||
this.toDispose.push(this.selectBoxDelegate);
|
||||
}
|
||||
|
||||
// Public SelectBox Methods - routed through delegate interface
|
||||
|
||||
public get onDidSelect(): Event<ISelectData> {
|
||||
return this._onDidSelect.event;
|
||||
return this.selectBoxDelegate.onDidSelect;
|
||||
}
|
||||
|
||||
public setOptions(options: string[], selected?: number, disabled?: number): void {
|
||||
if (!this.options || !arrays.equals(this.options, options)) {
|
||||
this.options = options;
|
||||
|
||||
this.selectElement.options.length = 0;
|
||||
let i = 0;
|
||||
this.options.forEach((option) => {
|
||||
this.selectElement.add(this.createOption(option, disabled === i++));
|
||||
});
|
||||
}
|
||||
this.select(selected);
|
||||
this.selectBoxDelegate.setOptions(options, selected, disabled);
|
||||
}
|
||||
|
||||
public select(index: number): void {
|
||||
if (index >= 0 && index < this.options.length) {
|
||||
this.selected = index;
|
||||
} else if (this.selected < 0) {
|
||||
this.selected = 0;
|
||||
}
|
||||
|
||||
this.selectElement.selectedIndex = this.selected;
|
||||
this.selectElement.title = this.options[this.selected];
|
||||
this.selectBoxDelegate.select(index);
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
if (this.selectElement) {
|
||||
this.selectElement.focus();
|
||||
}
|
||||
this.selectBoxDelegate.focus();
|
||||
}
|
||||
|
||||
public blur(): void {
|
||||
if (this.selectElement) {
|
||||
this.selectElement.blur();
|
||||
}
|
||||
this.selectBoxDelegate.blur();
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
dom.addClass(container, 'select-container');
|
||||
container.appendChild(this.selectElement);
|
||||
this.setOptions(this.options, this.selected);
|
||||
// Public Widget Methods - routed through delegate interface
|
||||
|
||||
this.applyStyles();
|
||||
public render(container: HTMLElement): void {
|
||||
this.selectBoxDelegate.render(container);
|
||||
}
|
||||
|
||||
public style(styles: ISelectBoxStyles): void {
|
||||
this.selectBackground = styles.selectBackground;
|
||||
this.selectForeground = styles.selectForeground;
|
||||
this.selectBorder = styles.selectBorder;
|
||||
|
||||
this.applyStyles();
|
||||
this.selectBoxDelegate.style(styles);
|
||||
}
|
||||
|
||||
protected applyStyles(): void {
|
||||
if (this.selectElement) {
|
||||
const background = this.selectBackground ? this.selectBackground.toString() : null;
|
||||
const foreground = this.selectForeground ? this.selectForeground.toString() : null;
|
||||
const border = this.selectBorder ? this.selectBorder.toString() : null;
|
||||
|
||||
this.selectElement.style.backgroundColor = background;
|
||||
this.selectElement.style.color = foreground;
|
||||
this.selectElement.style.borderColor = border;
|
||||
}
|
||||
public applyStyles(): void {
|
||||
this.selectBoxDelegate.applyStyles();
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
@@ -155,4 +138,4 @@ export class SelectBox extends Widget {
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* Require .monaco-shell for ContextView dropdown */
|
||||
|
||||
.monaco-shell .select-box-dropdown-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-shell .select-box-dropdown-container.visible {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: left;
|
||||
width: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container {
|
||||
flex: 0 0 auto;
|
||||
align-self: flex-start;
|
||||
padding-bottom: 1px;
|
||||
padding-top: 1px;
|
||||
padding-left: 1px;
|
||||
padding-right: 1px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
-webkit-box-sizing: border-box;
|
||||
-o-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.monaco-shell.hc-black .select-box-dropdown-container > .select-box-dropdown-list-container {
|
||||
padding-bottom: 4px;
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row > .option-text {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
padding-left: 3.5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.monaco-shell .select-box-dropdown-container > .select-box-dropdown-container-width-control {
|
||||
flex: 1 1 auto;
|
||||
align-self: flex-start;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.monaco-shell .select-box-dropdown-container > .select-box-dropdown-container-width-control > .width-control-div {
|
||||
overflow: hidden;
|
||||
max-height: 0px;
|
||||
}
|
||||
|
||||
.monaco-shell .select-box-dropdown-container > .select-box-dropdown-container-width-control > .width-control-div > .option-text-width-control {
|
||||
padding-left: 4px;
|
||||
padding-right: 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -0,0 +1,698 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./selectBoxCustom';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import Event, { Emitter, chain } from 'vs/base/common/event';
|
||||
import { KeyCode, KeyCodeUtils } from 'vs/base/common/keyCodes';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { List } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IDelegate, IRenderer } from 'vs/base/browser/ui/list/list';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { ISelectBoxDelegate, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
const SELECT_OPTION_ENTRY_TEMPLATE_ID = 'selectOption.entry.template';
|
||||
|
||||
export interface ISelectOptionItem {
|
||||
optionText: string;
|
||||
optionDisabled: boolean;
|
||||
}
|
||||
|
||||
interface ISelectListTemplateData {
|
||||
root: HTMLElement;
|
||||
optionText: HTMLElement;
|
||||
disposables: IDisposable[];
|
||||
}
|
||||
|
||||
class SelectListRenderer implements IRenderer<ISelectOptionItem, ISelectListTemplateData> {
|
||||
|
||||
get templateId(): string { return SELECT_OPTION_ENTRY_TEMPLATE_ID; }
|
||||
|
||||
constructor() { }
|
||||
|
||||
renderTemplate(container: HTMLElement): any {
|
||||
const data = <ISelectListTemplateData>Object.create(null);
|
||||
data.disposables = [];
|
||||
data.root = container;
|
||||
data.optionText = dom.append(container, $('.option-text'));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
renderElement(element: ISelectOptionItem, index: number, templateData: ISelectListTemplateData): void {
|
||||
const data = <ISelectListTemplateData>templateData;
|
||||
const optionText = (<ISelectOptionItem>element).optionText;
|
||||
const optionDisabled = (<ISelectOptionItem>element).optionDisabled;
|
||||
|
||||
data.optionText.textContent = optionText;
|
||||
data.root.setAttribute('aria-label', nls.localize('selectAriaOption', "{0}", optionText));
|
||||
|
||||
// pseudo-select disabled option
|
||||
if (optionDisabled) {
|
||||
dom.addClass((<HTMLElement>data.root), 'option-disabled');
|
||||
}
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: ISelectListTemplateData): void {
|
||||
templateData.disposables = dispose(templateData.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
export class SelectBoxList implements ISelectBoxDelegate, IDelegate<ISelectOptionItem> {
|
||||
|
||||
private static SELECT_DROPDOWN_BOTTOM_MARGIN = 10;
|
||||
|
||||
private _isVisible: boolean;
|
||||
// {{SQL CARBON EDIT}}
|
||||
public selectElement: HTMLSelectElement;
|
||||
private options: string[];
|
||||
private selected: number;
|
||||
private disabledOptionIndex: number;
|
||||
private _onDidSelect: Emitter<ISelectData>;
|
||||
private toDispose: IDisposable[];
|
||||
private styles: ISelectBoxStyles;
|
||||
private listRenderer: SelectListRenderer;
|
||||
private contextViewProvider: IContextViewProvider;
|
||||
private selectDropDownContainer: HTMLElement;
|
||||
private styleElement: HTMLStyleElement;
|
||||
private selectList: List<ISelectOptionItem>;
|
||||
private selectDropDownListContainer: HTMLElement;
|
||||
private widthControlElement: HTMLElement;
|
||||
private _currentSelection: number;
|
||||
|
||||
constructor(options: string[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles) {
|
||||
|
||||
this.toDispose = [];
|
||||
this._isVisible = false;
|
||||
|
||||
this.selectElement = document.createElement('select');
|
||||
this.selectElement.className = 'select-box';
|
||||
|
||||
this._onDidSelect = new Emitter<ISelectData>();
|
||||
this.styles = styles;
|
||||
|
||||
this.registerListeners();
|
||||
this.constructSelectDropDown(contextViewProvider);
|
||||
|
||||
this.setOptions(options, selected);
|
||||
}
|
||||
|
||||
// IDelegate - List renderer
|
||||
|
||||
getHeight(): number {
|
||||
return 18;
|
||||
}
|
||||
|
||||
getTemplateId(): string {
|
||||
return SELECT_OPTION_ENTRY_TEMPLATE_ID;
|
||||
}
|
||||
|
||||
private constructSelectDropDown(contextViewProvider: IContextViewProvider) {
|
||||
|
||||
// SetUp ContextView container to hold select Dropdown
|
||||
this.contextViewProvider = contextViewProvider;
|
||||
this.selectDropDownContainer = dom.$('.select-box-dropdown-container');
|
||||
|
||||
// Setup list for drop-down select
|
||||
this.createSelectList(this.selectDropDownContainer);
|
||||
|
||||
// Create span flex box item/div we can measure and control
|
||||
let widthControlOuterDiv = dom.append(this.selectDropDownContainer, $('.select-box-dropdown-container-width-control'));
|
||||
let widthControlInnerDiv = dom.append(widthControlOuterDiv, $('.width-control-div'));
|
||||
this.widthControlElement = document.createElement('span');
|
||||
this.widthControlElement.className = 'option-text-width-control';
|
||||
dom.append(widthControlInnerDiv, this.widthControlElement);
|
||||
|
||||
// Inline stylesheet for themes
|
||||
this.styleElement = dom.createStyleSheet(this.selectDropDownContainer);
|
||||
}
|
||||
|
||||
private registerListeners() {
|
||||
|
||||
// Parent native select keyboard listeners
|
||||
|
||||
this.toDispose.push(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => {
|
||||
this.selectElement.title = e.target.value;
|
||||
this._onDidSelect.fire({
|
||||
index: e.target.selectedIndex,
|
||||
selected: e.target.value
|
||||
});
|
||||
}));
|
||||
|
||||
// Have to implement both keyboard and mouse controllers to handle disabled options
|
||||
// Intercept mouse events to override normal select actions on parents
|
||||
|
||||
this.toDispose.push(dom.addDisposableListener(this.selectElement, dom.EventType.CLICK, (e) => {
|
||||
dom.EventHelper.stop(e);
|
||||
|
||||
if (this._isVisible) {
|
||||
this.hideSelectDropDown(true);
|
||||
} else {
|
||||
this.showSelectDropDown();
|
||||
}
|
||||
}));
|
||||
|
||||
this.toDispose.push(dom.addDisposableListener(this.selectElement, dom.EventType.MOUSE_DOWN, (e) => {
|
||||
dom.EventHelper.stop(e);
|
||||
}));
|
||||
|
||||
// Intercept keyboard handling
|
||||
|
||||
this.toDispose.push(dom.addDisposableListener(this.selectElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
let showDropDown = false;
|
||||
|
||||
// Create and drop down select list on keyboard select
|
||||
if (isMacintosh) {
|
||||
if (event.keyCode === KeyCode.DownArrow || event.keyCode === KeyCode.UpArrow || event.keyCode === KeyCode.Space || event.keyCode === KeyCode.Enter) {
|
||||
showDropDown = true;
|
||||
}
|
||||
} else {
|
||||
if (event.keyCode === KeyCode.DownArrow && event.altKey || event.keyCode === KeyCode.UpArrow && event.altKey || event.keyCode === KeyCode.Space || event.keyCode === KeyCode.Enter) {
|
||||
showDropDown = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (showDropDown) {
|
||||
this.showSelectDropDown();
|
||||
dom.EventHelper.stop(e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public get onDidSelect(): Event<ISelectData> {
|
||||
return this._onDidSelect.event;
|
||||
}
|
||||
|
||||
public setOptions(options: string[], selected?: number, disabled?: number): void {
|
||||
|
||||
if (!this.options || !arrays.equals(this.options, options)) {
|
||||
this.options = options;
|
||||
this.selectElement.options.length = 0;
|
||||
|
||||
let i = 0;
|
||||
this.options.forEach((option) => {
|
||||
this.selectElement.add(this.createOption(option, i, disabled === i++));
|
||||
});
|
||||
|
||||
// Mirror options in drop-down
|
||||
// Populate select list for non-native select mode
|
||||
if (this.selectList && !!this.options) {
|
||||
let listEntries: ISelectOptionItem[];
|
||||
|
||||
listEntries = [];
|
||||
if (disabled !== undefined) {
|
||||
this.disabledOptionIndex = disabled;
|
||||
}
|
||||
for (let index = 0; index < this.options.length; index++) {
|
||||
const element = this.options[index];
|
||||
let optionDisabled: boolean;
|
||||
index === this.disabledOptionIndex ? optionDisabled = true : optionDisabled = false;
|
||||
listEntries.push({ optionText: element, optionDisabled: optionDisabled });
|
||||
}
|
||||
|
||||
this.selectList.splice(0, this.selectList.length, listEntries);
|
||||
}
|
||||
}
|
||||
|
||||
if (selected !== undefined) {
|
||||
this.select(selected);
|
||||
}
|
||||
}
|
||||
|
||||
public select(index: number): void {
|
||||
|
||||
if (index >= 0 && index < this.options.length) {
|
||||
this.selected = index;
|
||||
} else if (this.selected < 0) {
|
||||
this.selected = 0;
|
||||
}
|
||||
|
||||
this.selectElement.selectedIndex = this.selected;
|
||||
this.selectElement.title = this.options[this.selected];
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
if (this.selectElement) {
|
||||
this.selectElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public blur(): void {
|
||||
if (this.selectElement) {
|
||||
this.selectElement.blur();
|
||||
}
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
dom.addClass(container, 'select-container');
|
||||
container.appendChild(this.selectElement);
|
||||
this.setOptions(this.options, this.selected);
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
public style(styles: ISelectBoxStyles): void {
|
||||
|
||||
const content: string[] = [];
|
||||
|
||||
this.styles = styles;
|
||||
|
||||
// Style non-native select mode
|
||||
|
||||
if (this.styles.listFocusBackground) {
|
||||
content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.focused { background-color: ${this.styles.listFocusBackground} !important; }`);
|
||||
}
|
||||
|
||||
if (this.styles.listFocusForeground) {
|
||||
content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.focused:not(:hover) { color: ${this.styles.listFocusForeground} !important; }`);
|
||||
}
|
||||
|
||||
// Hover foreground - ignore for disabled options
|
||||
if (this.styles.listHoverForeground) {
|
||||
content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row:hover { color: ${this.styles.listHoverForeground} !important; }`);
|
||||
content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.option-disabled:hover { background-color: ${this.styles.listActiveSelectionForeground} !important; }`);
|
||||
}
|
||||
|
||||
// Hover background - ignore for disabled options
|
||||
if (this.styles.listHoverBackground) {
|
||||
content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row:not(.option-disabled):not(.focused):hover { background-color: ${this.styles.listHoverBackground} !important; }`);
|
||||
content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.option-disabled:hover { background-color: ${this.styles.selectBackground} !important; }`);
|
||||
}
|
||||
|
||||
// Match quickOpen outline styles - ignore for disabled options
|
||||
if (this.styles.listFocusOutline) {
|
||||
content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.focused { outline: 1.6px dotted ${this.styles.listFocusOutline} !important; outline-offset: -1.6px !important; }`);
|
||||
}
|
||||
|
||||
if (this.styles.listHoverOutline) {
|
||||
content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row:hover:not(.focused) { outline: 1.6px dashed ${this.styles.listHoverOutline} !important; outline-offset: -1.6px !important; }`);
|
||||
content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.option-disabled:hover { outline: none !important; }`);
|
||||
}
|
||||
|
||||
this.styleElement.innerHTML = content.join('\n');
|
||||
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
public applyStyles(): void {
|
||||
|
||||
// Style parent select
|
||||
|
||||
let background = null;
|
||||
|
||||
if (this.selectElement) {
|
||||
background = this.styles.selectBackground ? this.styles.selectBackground.toString() : null;
|
||||
const foreground = this.styles.selectForeground ? this.styles.selectForeground.toString() : null;
|
||||
const border = this.styles.selectBorder ? this.styles.selectBorder.toString() : null;
|
||||
|
||||
this.selectElement.style.backgroundColor = background;
|
||||
this.selectElement.style.color = foreground;
|
||||
this.selectElement.style.borderColor = border;
|
||||
}
|
||||
|
||||
// Style drop down select list (non-native mode only)
|
||||
|
||||
if (this.selectList) {
|
||||
this.selectList.style({});
|
||||
|
||||
let listBackground = this.styles.selectListBackground ? this.styles.selectListBackground.toString() : background;
|
||||
this.selectDropDownListContainer.style.backgroundColor = listBackground;
|
||||
const optionsBorder = this.styles.focusBorder ? this.styles.focusBorder.toString() : null;
|
||||
this.selectDropDownContainer.style.outlineColor = optionsBorder;
|
||||
this.selectDropDownContainer.style.outlineOffset = '-1px';
|
||||
}
|
||||
}
|
||||
|
||||
private createOption(value: string, index: number, disabled?: boolean): HTMLOptionElement {
|
||||
let option = document.createElement('option');
|
||||
option.value = value;
|
||||
option.text = value;
|
||||
option.disabled = disabled;
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
// Non-native select list handling
|
||||
// ContextView dropdown methods
|
||||
|
||||
private showSelectDropDown() {
|
||||
if (!this.contextViewProvider || this._isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isVisible = true;
|
||||
this.cloneElementFont(this.selectElement, this.selectDropDownContainer);
|
||||
this.contextViewProvider.showContextView({
|
||||
getAnchor: () => this.selectElement,
|
||||
render: (container: HTMLElement) => this.renderSelectDropDown(container),
|
||||
layout: () => this.layoutSelectDropDown(),
|
||||
onHide: () => {
|
||||
dom.toggleClass(this.selectDropDownContainer, 'visible', false);
|
||||
dom.toggleClass(this.selectElement, 'synthetic-focus', false);
|
||||
}
|
||||
});
|
||||
this._currentSelection = this.selected;
|
||||
}
|
||||
|
||||
private hideSelectDropDown(focusSelect: boolean) {
|
||||
if (!this.contextViewProvider || !this._isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isVisible = false;
|
||||
|
||||
if (focusSelect) {
|
||||
this.selectElement.focus();
|
||||
}
|
||||
this.contextViewProvider.hideContextView();
|
||||
}
|
||||
|
||||
private renderSelectDropDown(container: HTMLElement): IDisposable {
|
||||
dom.append(container, this.selectDropDownContainer);
|
||||
this.layoutSelectDropDown();
|
||||
return null;
|
||||
}
|
||||
|
||||
private layoutSelectDropDown() {
|
||||
|
||||
// Layout ContextView drop down select list and container
|
||||
// Have to manage our vertical overflow, sizing
|
||||
// Need to be visible to measure
|
||||
|
||||
dom.toggleClass(this.selectDropDownContainer, 'visible', true);
|
||||
|
||||
const selectWidth = dom.getTotalWidth(this.selectElement);
|
||||
const selectPosition = dom.getDomNodePagePosition(this.selectElement);
|
||||
|
||||
// Set container height to max from select bottom to margin above status bar
|
||||
const statusBarHeight = dom.getTotalHeight(document.getElementById('workbench.parts.statusbar'));
|
||||
const maxSelectDropDownHeight = (window.innerHeight - selectPosition.top - selectPosition.height - statusBarHeight - SelectBoxList.SELECT_DROPDOWN_BOTTOM_MARGIN);
|
||||
|
||||
// SetUp list dimensions and layout - account for container padding
|
||||
if (this.selectList) {
|
||||
this.selectList.layout();
|
||||
let listHeight = this.selectList.contentHeight;
|
||||
const listContainerHeight = dom.getTotalHeight(this.selectDropDownListContainer);
|
||||
const totalVerticalListPadding = listContainerHeight - listHeight;
|
||||
|
||||
// Always show complete list items - never more than Max available vertical height
|
||||
if (listContainerHeight > maxSelectDropDownHeight) {
|
||||
listHeight = ((Math.floor((maxSelectDropDownHeight - totalVerticalListPadding) / this.getHeight())) * this.getHeight());
|
||||
}
|
||||
|
||||
this.selectList.layout(listHeight);
|
||||
this.selectList.domFocus();
|
||||
|
||||
// Finally set focus on selected item
|
||||
this.selectList.setFocus([this.selected]);
|
||||
this.selectList.reveal(this.selectList.getFocus()[0]);
|
||||
|
||||
// Set final container height after adjustments
|
||||
this.selectDropDownContainer.style.height = (listHeight + totalVerticalListPadding) + 'px';
|
||||
|
||||
// Determine optimal width - min(longest option), opt(parent select), max(ContextView controlled)
|
||||
const selectMinWidth = this.setWidthControlElement(this.widthControlElement);
|
||||
const selectOptimalWidth = Math.max(selectMinWidth, Math.round(selectWidth)).toString() + 'px';
|
||||
|
||||
this.selectDropDownContainer.style.minWidth = selectOptimalWidth;
|
||||
|
||||
// Maintain focus outline on parent select as well as list container - tabindex for focus
|
||||
this.selectDropDownListContainer.setAttribute('tabindex', '0');
|
||||
dom.toggleClass(this.selectElement, 'synthetic-focus', true);
|
||||
dom.toggleClass(this.selectDropDownContainer, 'synthetic-focus', true);
|
||||
}
|
||||
}
|
||||
|
||||
private setWidthControlElement(container: HTMLElement): number {
|
||||
let elementWidth = 0;
|
||||
|
||||
if (container && !!this.options) {
|
||||
let longest = 0;
|
||||
|
||||
for (let index = 0; index < this.options.length; index++) {
|
||||
if (this.options[index].length > this.options[longest].length) {
|
||||
longest = index;
|
||||
}
|
||||
}
|
||||
|
||||
container.innerHTML = this.options[longest];
|
||||
elementWidth = dom.getTotalWidth(container);
|
||||
}
|
||||
|
||||
return elementWidth;
|
||||
}
|
||||
|
||||
private cloneElementFont(source: HTMLElement, target: HTMLElement) {
|
||||
const fontSize = window.getComputedStyle(source, null).getPropertyValue('font-size');
|
||||
const fontFamily = window.getComputedStyle(source, null).getPropertyValue('font-family');
|
||||
target.style.fontFamily = fontFamily;
|
||||
target.style.fontSize = fontSize;
|
||||
}
|
||||
|
||||
private createSelectList(parent: HTMLElement): void {
|
||||
|
||||
// SetUp container for list
|
||||
this.selectDropDownListContainer = dom.append(parent, $('.select-box-dropdown-list-container'));
|
||||
|
||||
this.listRenderer = new SelectListRenderer();
|
||||
|
||||
this.selectList = new List(this.selectDropDownListContainer, this, [this.listRenderer], {
|
||||
useShadows: false,
|
||||
selectOnMouseDown: false,
|
||||
verticalScrollMode: ScrollbarVisibility.Visible,
|
||||
keyboardSupport: false,
|
||||
mouseSupport: false
|
||||
});
|
||||
|
||||
// SetUp list keyboard controller - control navigation, disabled items, focus
|
||||
const onSelectDropDownKeyDown = chain(domEvent(this.selectDropDownListContainer, 'keydown'))
|
||||
.filter(() => this.selectList.length > 0)
|
||||
.map(e => new StandardKeyboardEvent(e));
|
||||
|
||||
onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Enter).on(e => this.onEnter(e), this, this.toDispose);
|
||||
onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Escape).on(e => this.onEscape(e), this, this.toDispose);
|
||||
onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.UpArrow).on(this.onUpArrow, this, this.toDispose);
|
||||
onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.DownArrow).on(this.onDownArrow, this, this.toDispose);
|
||||
onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.PageDown).on(this.onPageDown, this, this.toDispose);
|
||||
onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.PageUp).on(this.onPageUp, this, this.toDispose);
|
||||
onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Home).on(this.onHome, this, this.toDispose);
|
||||
onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.End).on(this.onEnd, this, this.toDispose);
|
||||
onSelectDropDownKeyDown.filter(e => (e.keyCode >= KeyCode.KEY_0 && e.keyCode <= KeyCode.KEY_Z) || (e.keyCode >= KeyCode.US_SEMICOLON && e.keyCode <= KeyCode.NUMPAD_DIVIDE)).on(this.onCharacter, this, this.toDispose);
|
||||
|
||||
// SetUp list mouse controller - control navigation, disabled items, focus
|
||||
|
||||
chain(domEvent(this.selectList.getHTMLElement(), 'mouseup'))
|
||||
.filter(() => this.selectList.length > 0)
|
||||
.on(e => this.onMouseUp(e), this, this.toDispose);
|
||||
|
||||
this.toDispose.push(this.selectList.onDidBlur(e => this.onListBlur()));
|
||||
}
|
||||
|
||||
// List methods
|
||||
|
||||
// List mouse controller - active exit, select option, fire onDidSelect, return focus to parent select
|
||||
private onMouseUp(e: MouseEvent): void {
|
||||
|
||||
// Check our mouse event is on an option (not scrollbar)
|
||||
if (!e.toElement.classList.contains('option-text')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const listRowElement = e.toElement.parentElement;
|
||||
const index = Number(listRowElement.getAttribute('data-index'));
|
||||
const disabled = listRowElement.classList.contains('option-disabled');
|
||||
|
||||
// Ignore mouse selection of disabled options
|
||||
if (index >= 0 && index < this.options.length && !disabled) {
|
||||
this.selected = index;
|
||||
this.select(this.selected);
|
||||
|
||||
this.selectList.setFocus([this.selected]);
|
||||
this.selectList.reveal(this.selectList.getFocus()[0]);
|
||||
this._onDidSelect.fire({
|
||||
index: this.selectElement.selectedIndex,
|
||||
selected: this.selectElement.title
|
||||
});
|
||||
|
||||
// Reset Selection Handler
|
||||
this._currentSelection = -1;
|
||||
this.hideSelectDropDown(true);
|
||||
}
|
||||
dom.EventHelper.stop(e);
|
||||
}
|
||||
|
||||
// List Exit - passive - hide drop-down, fire onDidSelect
|
||||
private onListBlur(): void {
|
||||
|
||||
if (this._currentSelection >= 0) {
|
||||
this.select(this._currentSelection);
|
||||
}
|
||||
|
||||
this._onDidSelect.fire({
|
||||
index: this.selectElement.selectedIndex,
|
||||
selected: this.selectElement.title
|
||||
});
|
||||
|
||||
this.hideSelectDropDown(false);
|
||||
}
|
||||
|
||||
// List keyboard controller
|
||||
// List exit - active - hide ContextView dropdown, return focus to parent select, fire onDidSelect
|
||||
private onEscape(e: StandardKeyboardEvent): void {
|
||||
dom.EventHelper.stop(e);
|
||||
this.select(this._currentSelection);
|
||||
|
||||
this.hideSelectDropDown(true);
|
||||
|
||||
this._onDidSelect.fire({
|
||||
index: this.selectElement.selectedIndex,
|
||||
selected: this.selectElement.title
|
||||
});
|
||||
}
|
||||
|
||||
// List exit - active - hide ContextView dropdown, return focus to parent select, fire onDidSelect
|
||||
private onEnter(e: StandardKeyboardEvent): void {
|
||||
dom.EventHelper.stop(e);
|
||||
|
||||
// Reset current selection
|
||||
this._currentSelection = -1;
|
||||
|
||||
this.hideSelectDropDown(true);
|
||||
this._onDidSelect.fire({
|
||||
index: this.selectElement.selectedIndex,
|
||||
selected: this.selectElement.title
|
||||
});
|
||||
}
|
||||
|
||||
// List navigation - have to handle a disabled option (jump over)
|
||||
private onDownArrow(): void {
|
||||
if (this.selected < this.options.length - 1) {
|
||||
// Skip disabled options
|
||||
if ((this.selected + 1) === this.disabledOptionIndex && this.options.length > this.selected + 2) {
|
||||
this.selected += 2;
|
||||
} else {
|
||||
this.selected++;
|
||||
}
|
||||
// Set focus/selection - only fire event when closing drop-down or on blur
|
||||
this.select(this.selected);
|
||||
this.selectList.setFocus([this.selected]);
|
||||
this.selectList.reveal(this.selectList.getFocus()[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private onUpArrow(): void {
|
||||
if (this.selected > 0) {
|
||||
// Skip disabled options
|
||||
if ((this.selected - 1) === this.disabledOptionIndex && this.selected > 1) {
|
||||
this.selected -= 2;
|
||||
} else {
|
||||
this.selected--;
|
||||
}
|
||||
// Set focus/selection - only fire event when closing drop-down or on blur
|
||||
this.select(this.selected);
|
||||
this.selectList.setFocus([this.selected]);
|
||||
this.selectList.reveal(this.selectList.getFocus()[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private onPageUp(e: StandardKeyboardEvent): void {
|
||||
dom.EventHelper.stop(e);
|
||||
|
||||
this.selectList.focusPreviousPage();
|
||||
|
||||
// Allow scrolling to settle
|
||||
setTimeout(() => {
|
||||
this.selected = this.selectList.getFocus()[0];
|
||||
|
||||
// Shift selection down if we land on a disabled option
|
||||
if (this.selected === this.disabledOptionIndex && this.selected < this.options.length - 1) {
|
||||
this.selected++;
|
||||
this.selectList.setFocus([this.selected]);
|
||||
}
|
||||
this.selectList.reveal(this.selected);
|
||||
this.select(this.selected);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
private onPageDown(e: StandardKeyboardEvent): void {
|
||||
dom.EventHelper.stop(e);
|
||||
|
||||
this.selectList.focusNextPage();
|
||||
|
||||
// Allow scrolling to settle
|
||||
setTimeout(() => {
|
||||
this.selected = this.selectList.getFocus()[0];
|
||||
|
||||
// Shift selection up if we land on a disabled option
|
||||
if (this.selected === this.disabledOptionIndex && this.selected > 0) {
|
||||
this.selected--;
|
||||
this.selectList.setFocus([this.selected]);
|
||||
}
|
||||
this.selectList.reveal(this.selected);
|
||||
this.select(this.selected);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
private onHome(e: StandardKeyboardEvent): void {
|
||||
dom.EventHelper.stop(e);
|
||||
|
||||
if (this.options.length < 2) {
|
||||
return;
|
||||
}
|
||||
this.selected = 0;
|
||||
if (this.selected === this.disabledOptionIndex && this.selected > 1) {
|
||||
this.selected++;
|
||||
}
|
||||
this.selectList.setFocus([this.selected]);
|
||||
this.selectList.reveal(this.selected);
|
||||
this.select(this.selected);
|
||||
}
|
||||
|
||||
private onEnd(e: StandardKeyboardEvent): void {
|
||||
dom.EventHelper.stop(e);
|
||||
|
||||
if (this.options.length < 2) {
|
||||
return;
|
||||
}
|
||||
this.selected = this.options.length - 1;
|
||||
if (this.selected === this.disabledOptionIndex && this.selected > 1) {
|
||||
this.selected--;
|
||||
}
|
||||
this.selectList.setFocus([this.selected]);
|
||||
this.selectList.reveal(this.selected);
|
||||
this.select(this.selected);
|
||||
}
|
||||
|
||||
// Mimic option first character navigation of native select
|
||||
private onCharacter(e: StandardKeyboardEvent): void {
|
||||
const ch = KeyCodeUtils.toString(e.keyCode);
|
||||
let optionIndex = -1;
|
||||
|
||||
for (let i = 0; i < this.options.length - 1; i++) {
|
||||
optionIndex = (i + this.selected + 1) % this.options.length;
|
||||
if (this.options[optionIndex].charAt(0).toUpperCase() === ch) {
|
||||
this.select(optionIndex);
|
||||
this.selectList.setFocus([optionIndex]);
|
||||
this.selectList.reveal(this.selectList.getFocus()[0]);
|
||||
dom.EventHelper.stop(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.hideSelectDropDown(false);
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { ISelectBoxDelegate, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
|
||||
export class SelectBoxNative implements ISelectBoxDelegate {
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
public selectElement: HTMLSelectElement;
|
||||
private options: string[];
|
||||
private selected: number;
|
||||
private _onDidSelect: Emitter<ISelectData>;
|
||||
private toDispose: IDisposable[];
|
||||
private styles: ISelectBoxStyles;
|
||||
|
||||
constructor(options: string[], selected: number, styles: ISelectBoxStyles) {
|
||||
|
||||
this.toDispose = [];
|
||||
|
||||
this.selectElement = document.createElement('select');
|
||||
this.selectElement.className = 'select-box';
|
||||
|
||||
this._onDidSelect = new Emitter<ISelectData>();
|
||||
|
||||
this.styles = styles;
|
||||
|
||||
this.registerListeners();
|
||||
|
||||
this.setOptions(options, selected);
|
||||
}
|
||||
|
||||
private registerListeners() {
|
||||
|
||||
this.toDispose.push(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => {
|
||||
this.selectElement.title = e.target.value;
|
||||
this._onDidSelect.fire({
|
||||
index: e.target.selectedIndex,
|
||||
selected: e.target.value
|
||||
});
|
||||
}));
|
||||
|
||||
this.toDispose.push(dom.addStandardDisposableListener(this.selectElement, 'keydown', (e) => {
|
||||
let showSelect = false;
|
||||
|
||||
if (isMacintosh) {
|
||||
if (e.keyCode === KeyCode.DownArrow || e.keyCode === KeyCode.UpArrow || e.keyCode === KeyCode.Space) {
|
||||
showSelect = true;
|
||||
}
|
||||
} else {
|
||||
if (e.keyCode === KeyCode.DownArrow && e.altKey || e.keyCode === KeyCode.Space || e.keyCode === KeyCode.Enter) {
|
||||
showSelect = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (showSelect) {
|
||||
// Space, Enter, is used to expand select box, do not propagate it (prevent action bar action run)
|
||||
e.stopPropagation();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public get onDidSelect(): Event<ISelectData> {
|
||||
return this._onDidSelect.event;
|
||||
}
|
||||
|
||||
public setOptions(options: string[], selected?: number, disabled?: number): void {
|
||||
|
||||
if (!this.options || !arrays.equals(this.options, options)) {
|
||||
this.options = options;
|
||||
this.selectElement.options.length = 0;
|
||||
|
||||
let i = 0;
|
||||
this.options.forEach((option) => {
|
||||
this.selectElement.add(this.createOption(option, i, disabled === i++));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
if (selected !== undefined) {
|
||||
this.select(selected);
|
||||
}
|
||||
}
|
||||
|
||||
public select(index: number): void {
|
||||
if (index >= 0 && index < this.options.length) {
|
||||
this.selected = index;
|
||||
} else if (this.selected < 0) {
|
||||
this.selected = 0;
|
||||
}
|
||||
|
||||
this.selectElement.selectedIndex = this.selected;
|
||||
this.selectElement.title = this.options[this.selected];
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
if (this.selectElement) {
|
||||
this.selectElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public blur(): void {
|
||||
if (this.selectElement) {
|
||||
this.selectElement.blur();
|
||||
}
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
dom.addClass(container, 'select-container');
|
||||
container.appendChild(this.selectElement);
|
||||
this.setOptions(this.options, this.selected);
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
public style(styles: ISelectBoxStyles): void {
|
||||
this.styles = styles;
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
public applyStyles(): void {
|
||||
|
||||
// Style native select
|
||||
if (this.selectElement) {
|
||||
const background = this.styles.selectBackground ? this.styles.selectBackground.toString() : null;
|
||||
const foreground = this.styles.selectForeground ? this.styles.selectForeground.toString() : null;
|
||||
const border = this.styles.selectBorder ? this.styles.selectBorder.toString() : null;
|
||||
|
||||
this.selectElement.style.backgroundColor = background;
|
||||
this.selectElement.style.color = foreground;
|
||||
this.selectElement.style.borderColor = border;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private createOption(value: string, index: number, disabled?: boolean): HTMLOptionElement {
|
||||
let option = document.createElement('option');
|
||||
option.value = value;
|
||||
option.text = value;
|
||||
option.disabled = disabled;
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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, { anyEvent } from 'vs/base/common/event';
|
||||
import { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
import { append, $ } from 'vs/base/browser/dom';
|
||||
import { SplitView, IView } from 'vs/base/browser/ui/splitview/splitview';
|
||||
export { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
|
||||
export class GridNode implements IView {
|
||||
|
||||
get minimumSize(): number {
|
||||
let result = 0;
|
||||
|
||||
for (const child of this.children) {
|
||||
for (const grandchild of child.children) {
|
||||
result += grandchild.minimumSize;
|
||||
}
|
||||
}
|
||||
|
||||
return result === 0 ? 50 : result;
|
||||
}
|
||||
|
||||
readonly maximumSize = Number.MAX_VALUE;
|
||||
|
||||
private _onDidChange: Event<number | undefined> = Event.None;
|
||||
get onDidChange(): Event<number | undefined> {
|
||||
return this._onDidChange;
|
||||
}
|
||||
|
||||
protected orientation: Orientation | undefined;
|
||||
protected size: number | undefined;
|
||||
protected orthogonalSize: number | undefined;
|
||||
private splitview: SplitView | undefined;
|
||||
private children: GridNode[] = [];
|
||||
private color: string | undefined;
|
||||
|
||||
constructor(private parent?: GridNode, orthogonalSize?: number, color?: string) {
|
||||
this.orthogonalSize = orthogonalSize;
|
||||
this.color = color || `hsl(${Math.round(Math.random() * 360)}, 72%, 72%)`;
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
container = append(container, $('.node'));
|
||||
container.style.backgroundColor = this.color;
|
||||
|
||||
append(container, $('.action', { onclick: () => this.split(container, Orientation.HORIZONTAL) }, '⬌'));
|
||||
append(container, $('.action', { onclick: () => this.split(container, Orientation.VERTICAL) }, '⬍'));
|
||||
}
|
||||
|
||||
protected split(container: HTMLElement, orientation: Orientation): void {
|
||||
if (this.parent && this.parent.orientation === orientation) {
|
||||
const index = this.parent.children.indexOf(this);
|
||||
this.parent.addChild(this.size / 2, this.orthogonalSize, index + 1);
|
||||
} else {
|
||||
this.branch(container, orientation);
|
||||
}
|
||||
}
|
||||
|
||||
protected branch(container: HTMLElement, orientation: Orientation): void {
|
||||
this.orientation = orientation;
|
||||
container.innerHTML = '';
|
||||
|
||||
this.splitview = new SplitView(container, { orientation });
|
||||
this.layout(this.size);
|
||||
this.orthogonalLayout(this.orthogonalSize);
|
||||
|
||||
this.addChild(this.orthogonalSize / 2, this.size, 0, this.color);
|
||||
this.addChild(this.orthogonalSize / 2, this.size);
|
||||
}
|
||||
|
||||
layout(size: number): void {
|
||||
this.size = size;
|
||||
|
||||
for (const child of this.children) {
|
||||
child.orthogonalLayout(size);
|
||||
}
|
||||
}
|
||||
|
||||
orthogonalLayout(size: number): void {
|
||||
this.orthogonalSize = size;
|
||||
|
||||
if (this.splitview) {
|
||||
this.splitview.layout(size);
|
||||
}
|
||||
}
|
||||
|
||||
private addChild(size: number, orthogonalSize: number, index?: number, color?: string): void {
|
||||
const child = new GridNode(this, orthogonalSize, color);
|
||||
this.splitview.addView(child, size, index);
|
||||
|
||||
if (typeof index === 'number') {
|
||||
this.children.splice(index, 0, child);
|
||||
} else {
|
||||
this.children.push(child);
|
||||
}
|
||||
|
||||
this._onDidChange = anyEvent(...this.children.map(c => c.onDidChange));
|
||||
}
|
||||
}
|
||||
|
||||
export class RootGridNode extends GridNode {
|
||||
|
||||
private width: number;
|
||||
private height: number;
|
||||
|
||||
protected branch(container: HTMLElement, orientation: Orientation): void {
|
||||
if (orientation === Orientation.VERTICAL) {
|
||||
this.size = this.width;
|
||||
this.orthogonalSize = this.height;
|
||||
} else {
|
||||
this.size = this.height;
|
||||
this.orthogonalSize = this.width;
|
||||
}
|
||||
|
||||
super.branch(container, orientation);
|
||||
}
|
||||
|
||||
layoutBox(width: number, height: number): void {
|
||||
if (this.orientation === Orientation.VERTICAL) {
|
||||
this.layout(width);
|
||||
this.orthogonalLayout(height);
|
||||
} else if (this.orientation === Orientation.HORIZONTAL) {
|
||||
this.layout(height);
|
||||
this.orthogonalLayout(width);
|
||||
} else {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Grid {
|
||||
|
||||
private root: RootGridNode;
|
||||
|
||||
constructor(container: HTMLElement) {
|
||||
this.root = new RootGridNode();
|
||||
this.root.render(container);
|
||||
}
|
||||
|
||||
layout(width: number, height: number): void {
|
||||
this.root.layoutBox(width, height);
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@
|
||||
|
||||
/* TODO: actions should be part of the panel, but they aren't yet */
|
||||
.monaco-panel-view .panel:hover > .panel-header.expanded > .actions,
|
||||
.monaco-panel-view .panel > .panel-header.actions-always-visible.expanded > .actions,
|
||||
.monaco-panel-view .panel > .panel-header.focused.expanded > .actions {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,10 @@ export abstract class Panel implements IView {
|
||||
private _onDidChange = new Emitter<number | undefined>();
|
||||
readonly onDidChange: Event<number | undefined> = this._onDidChange.event;
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this.el;
|
||||
}
|
||||
|
||||
get draggableElement(): HTMLElement {
|
||||
return this.header;
|
||||
}
|
||||
|
||||
@@ -20,4 +20,5 @@
|
||||
|
||||
.monaco-split-view2.horizontal > .split-view-view {
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@@ -91,10 +91,13 @@ export class SplitView implements IDisposable {
|
||||
|
||||
private _onDidSashChange = new Emitter<void>();
|
||||
readonly onDidSashChange = this._onDidSashChange.event;
|
||||
private _onDidSashReset = new Emitter<void>();
|
||||
readonly onDidSashReset = this._onDidSashReset.event;
|
||||
|
||||
get length(): number {
|
||||
return this.viewItems.length;
|
||||
}
|
||||
|
||||
constructor(container: HTMLElement, options: ISplitViewOptions = {}) {
|
||||
this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation;
|
||||
|
||||
@@ -152,15 +155,17 @@ export class SplitView implements IDisposable {
|
||||
const onSashChangeDisposable = onChange(this.onSashChange, this);
|
||||
const onEnd = mapEvent<void, void>(sash.onDidEnd, () => null);
|
||||
const onEndDisposable = onEnd(() => this._onDidSashChange.fire());
|
||||
const onDidReset = mapEvent<void, void>(sash.onDidReset, () => null);
|
||||
const onDidResetDisposable = onDidReset(() => this._onDidSashReset.fire());
|
||||
|
||||
const disposable = combinedDisposable([onStartDisposable, onSashChangeDisposable, onEndDisposable, sash]);
|
||||
const disposable = combinedDisposable([onStartDisposable, onSashChangeDisposable, onEndDisposable, onDidResetDisposable, sash]);
|
||||
const sashItem: ISashItem = { sash, disposable };
|
||||
|
||||
this.sashItems.splice(index - 1, 0, sashItem);
|
||||
}
|
||||
|
||||
view.render(container, this.orientation);
|
||||
this.relayout();
|
||||
this.relayout(index);
|
||||
this.state = State.Idle;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-toolbar .dropdown > .dropdown-label:not(:empty) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.monaco-toolbar .toolbar-toggle-more {
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
|
||||
@@ -8,12 +8,10 @@
|
||||
import 'vs/css!./toolbar';
|
||||
import nls = require('vs/nls');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import types = require('vs/base/common/types');
|
||||
import { Action, IActionRunner, IAction } from 'vs/base/common/actions';
|
||||
import { ActionBar, ActionsOrientation, IActionItemProvider, BaseActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IContextMenuProvider, DropdownMenu, IActionProvider, ILabelRenderer, IDropdownMenuOptions } from 'vs/base/browser/ui/dropdown/dropdown';
|
||||
import { ActionBar, ActionsOrientation, IActionItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IContextMenuProvider, DropdownMenuActionItem } from 'vs/base/browser/ui/dropdown/dropdown';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
|
||||
export const CONTEXT = 'context.toolbar';
|
||||
@@ -181,83 +179,4 @@ class ToggleMenuAction extends Action {
|
||||
public set menuActions(actions: IAction[]) {
|
||||
this._menuActions = actions;
|
||||
}
|
||||
}
|
||||
|
||||
export class DropdownMenuActionItem extends BaseActionItem {
|
||||
private menuActionsOrProvider: any;
|
||||
private dropdownMenu: DropdownMenu;
|
||||
private contextMenuProvider: IContextMenuProvider;
|
||||
private actionItemProvider: IActionItemProvider;
|
||||
private keybindings: (action: IAction) => ResolvedKeybinding;
|
||||
private clazz: string;
|
||||
|
||||
constructor(action: IAction, menuActions: IAction[], contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, actionRunner: IActionRunner, keybindings: (action: IAction) => ResolvedKeybinding, clazz: string);
|
||||
constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, actionRunner: IActionRunner, keybindings: (action: IAction) => ResolvedKeybinding, clazz: string);
|
||||
constructor(action: IAction, menuActionsOrProvider: any, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, actionRunner: IActionRunner, keybindings: (action: IAction) => ResolvedKeybinding, clazz: string) {
|
||||
super(null, action);
|
||||
|
||||
this.menuActionsOrProvider = menuActionsOrProvider;
|
||||
this.contextMenuProvider = contextMenuProvider;
|
||||
this.actionItemProvider = actionItemProvider;
|
||||
this.actionRunner = actionRunner;
|
||||
this.keybindings = keybindings;
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
let labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable => {
|
||||
this.builder = $('a.action-label').attr({
|
||||
tabIndex: '0',
|
||||
role: 'button',
|
||||
'aria-haspopup': 'true',
|
||||
title: this._action.label || '',
|
||||
class: this.clazz
|
||||
});
|
||||
|
||||
this.builder.appendTo(el);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
let options: IDropdownMenuOptions = {
|
||||
contextMenuProvider: this.contextMenuProvider,
|
||||
labelRenderer: labelRenderer
|
||||
};
|
||||
|
||||
// Render the DropdownMenu around a simple action to toggle it
|
||||
if (types.isArray(this.menuActionsOrProvider)) {
|
||||
options.actions = this.menuActionsOrProvider;
|
||||
} else {
|
||||
options.actionProvider = this.menuActionsOrProvider;
|
||||
}
|
||||
|
||||
this.dropdownMenu = new DropdownMenu(container, options);
|
||||
|
||||
this.dropdownMenu.menuOptions = {
|
||||
actionItemProvider: this.actionItemProvider,
|
||||
actionRunner: this.actionRunner,
|
||||
getKeyBinding: this.keybindings,
|
||||
context: this._context
|
||||
};
|
||||
}
|
||||
|
||||
public setActionContext(newContext: any): void {
|
||||
super.setActionContext(newContext);
|
||||
|
||||
if (this.dropdownMenu) {
|
||||
this.dropdownMenu.menuOptions.context = newContext;
|
||||
}
|
||||
}
|
||||
|
||||
public show(): void {
|
||||
if (this.dropdownMenu) {
|
||||
this.dropdownMenu.show();
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.dropdownMenu.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -233,6 +233,7 @@ export class ActionRunner implements IActionRunner {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
// noop
|
||||
this._onDidBeforeRun.dispose();
|
||||
this._onDidRun.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,3 +438,20 @@ export function arrayInsert<T>(target: T[], insertIndex: number, insertArr: T[])
|
||||
const after = target.slice(insertIndex);
|
||||
return before.concat(insertArr, after);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses Fisher-Yates shuffle to shuffle the given array
|
||||
* @param array
|
||||
*/
|
||||
export function shuffle<T>(array: T[]): void {
|
||||
var i = 0
|
||||
, j = 0
|
||||
, temp = null;
|
||||
|
||||
for (i = array.length - 1; i > 0; i -= 1) {
|
||||
j = Math.floor(Math.random() * (i + 1));
|
||||
temp = array[i];
|
||||
array[i] = array[j];
|
||||
array[j] = temp;
|
||||
}
|
||||
}
|
||||
+25
-18
@@ -6,7 +6,6 @@
|
||||
'use strict';
|
||||
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { Promise, TPromise, ValueCallback, ErrorCallback, ProgressCallback } from 'vs/base/common/winjs.base';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
@@ -181,7 +180,7 @@ export class Delayer<T> {
|
||||
private timeout: number;
|
||||
private completionPromise: Promise;
|
||||
private onSuccess: ValueCallback;
|
||||
private task: ITask<T>;
|
||||
private task: ITask<T | TPromise<T>>;
|
||||
|
||||
constructor(public defaultDelay: number) {
|
||||
this.timeout = null;
|
||||
@@ -190,7 +189,7 @@ export class Delayer<T> {
|
||||
this.task = null;
|
||||
}
|
||||
|
||||
trigger(task: ITask<T>, delay: number = this.defaultDelay): TPromise<T> {
|
||||
trigger(task: ITask<T | TPromise<T>>, delay: number = this.defaultDelay): TPromise<T> {
|
||||
this.task = task;
|
||||
this.cancelTimeout();
|
||||
|
||||
@@ -255,7 +254,7 @@ export class ThrottledDelayer<T> extends Delayer<TPromise<T>> {
|
||||
this.throttler = new Throttler();
|
||||
}
|
||||
|
||||
trigger(promiseFactory: ITask<TPromise<T>>, delay?: number): Promise {
|
||||
trigger(promiseFactory: ITask<TPromise<T>>, delay?: number): TPromise {
|
||||
return super.trigger(() => this.throttler.queue(promiseFactory), delay);
|
||||
}
|
||||
}
|
||||
@@ -314,6 +313,13 @@ export class ShallowCancelThenPromise<T> extends TPromise<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement for `WinJS.Promise.timeout`.
|
||||
*/
|
||||
export function timeout(n: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, n));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new promise that joins the provided promise. Upon completion of
|
||||
* the provided promise the provided function will always be called. This
|
||||
@@ -349,13 +355,14 @@ export function always<T>(promise: TPromise<T>, f: Function): TPromise<T> {
|
||||
* Runs the provided list of promise factories in sequential order. The returned
|
||||
* promise will complete to an array of results from each promise.
|
||||
*/
|
||||
export function sequence<T>(promiseFactories: ITask<TPromise<T>>[]): TPromise<T[]> {
|
||||
|
||||
export function sequence<T>(promiseFactories: ITask<Thenable<T>>[]): TPromise<T[]> {
|
||||
const results: T[] = [];
|
||||
|
||||
// reverse since we start with last element using pop()
|
||||
promiseFactories = promiseFactories.reverse();
|
||||
|
||||
function next(): Promise {
|
||||
function next(): Thenable<any> {
|
||||
if (promiseFactories.length) {
|
||||
return promiseFactories.pop()();
|
||||
}
|
||||
@@ -363,7 +370,7 @@ export function sequence<T>(promiseFactories: ITask<TPromise<T>>[]): TPromise<T[
|
||||
return null;
|
||||
}
|
||||
|
||||
function thenHandler(result: any): Promise {
|
||||
function thenHandler(result: any): Thenable<any> {
|
||||
if (result !== undefined && result !== null) {
|
||||
results.push(result);
|
||||
}
|
||||
@@ -517,7 +524,7 @@ export function setDisposableTimeout(handler: Function, timeout: number, ...args
|
||||
}
|
||||
|
||||
export class TimeoutTimer extends Disposable {
|
||||
private _token: platform.TimeoutToken;
|
||||
private _token: number;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -531,14 +538,14 @@ export class TimeoutTimer extends Disposable {
|
||||
|
||||
cancel(): void {
|
||||
if (this._token !== -1) {
|
||||
platform.clearTimeout(this._token);
|
||||
clearTimeout(this._token);
|
||||
this._token = -1;
|
||||
}
|
||||
}
|
||||
|
||||
cancelAndSet(runner: () => void, timeout: number): void {
|
||||
this.cancel();
|
||||
this._token = platform.setTimeout(() => {
|
||||
this._token = setTimeout(() => {
|
||||
this._token = -1;
|
||||
runner();
|
||||
}, timeout);
|
||||
@@ -549,7 +556,7 @@ export class TimeoutTimer extends Disposable {
|
||||
// timer is already set
|
||||
return;
|
||||
}
|
||||
this._token = platform.setTimeout(() => {
|
||||
this._token = setTimeout(() => {
|
||||
this._token = -1;
|
||||
runner();
|
||||
}, timeout);
|
||||
@@ -558,7 +565,7 @@ export class TimeoutTimer extends Disposable {
|
||||
|
||||
export class IntervalTimer extends Disposable {
|
||||
|
||||
private _token: platform.IntervalToken;
|
||||
private _token: number;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -572,14 +579,14 @@ export class IntervalTimer extends Disposable {
|
||||
|
||||
cancel(): void {
|
||||
if (this._token !== -1) {
|
||||
platform.clearInterval(this._token);
|
||||
clearInterval(this._token);
|
||||
this._token = -1;
|
||||
}
|
||||
}
|
||||
|
||||
cancelAndSet(runner: () => void, interval: number): void {
|
||||
this.cancel();
|
||||
this._token = platform.setInterval(() => {
|
||||
this._token = setInterval(() => {
|
||||
runner();
|
||||
}, interval);
|
||||
}
|
||||
@@ -587,7 +594,7 @@ export class IntervalTimer extends Disposable {
|
||||
|
||||
export class RunOnceScheduler {
|
||||
|
||||
private timeoutToken: platform.TimeoutToken;
|
||||
private timeoutToken: number;
|
||||
private runner: () => void;
|
||||
private timeout: number;
|
||||
private timeoutHandler: () => void;
|
||||
@@ -612,7 +619,7 @@ export class RunOnceScheduler {
|
||||
*/
|
||||
cancel(): void {
|
||||
if (this.isScheduled()) {
|
||||
platform.clearTimeout(this.timeoutToken);
|
||||
clearTimeout(this.timeoutToken);
|
||||
this.timeoutToken = -1;
|
||||
}
|
||||
}
|
||||
@@ -622,7 +629,7 @@ export class RunOnceScheduler {
|
||||
*/
|
||||
schedule(delay = this.timeout): void {
|
||||
this.cancel();
|
||||
this.timeoutToken = platform.setTimeout(this.timeoutHandler, delay);
|
||||
this.timeoutToken = setTimeout(this.timeoutHandler, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -692,4 +699,4 @@ export class ThrottledEmitter<T> extends Emitter<T> {
|
||||
this.hasLastEvent = false;
|
||||
this.lastEvent = void 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,6 +399,22 @@ export class Color {
|
||||
return new Color(new RGBA(r, g, b, a));
|
||||
}
|
||||
|
||||
flatten(...backgrounds: Color[]): Color {
|
||||
const background = backgrounds.reduceRight((accumulator, color) => {
|
||||
return Color._flatten(color, accumulator);
|
||||
});
|
||||
return Color._flatten(this, background);
|
||||
}
|
||||
|
||||
private static _flatten(foreground: Color, background: Color) {
|
||||
const backgroundAlpha = 1 - foreground.rgba.a;
|
||||
return new Color(new RGBA(
|
||||
backgroundAlpha * background.rgba.r + foreground.rgba.a * foreground.rgba.r,
|
||||
backgroundAlpha * background.rgba.g + foreground.rgba.a * foreground.rgba.g,
|
||||
backgroundAlpha * background.rgba.b + foreground.rgba.a * foreground.rgba.b
|
||||
));
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return Color.Format.CSS.format(this);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export function setFileNameComparer(collator: Intl.Collator): void {
|
||||
intlFileNameCollatorIsNumeric = collator.resolvedOptions().numeric;
|
||||
}
|
||||
|
||||
export function compareFileNames(one: string, other: string): number {
|
||||
export function compareFileNames(one: string, other: string, caseSensitive = false): number {
|
||||
if (intlFileNameCollator) {
|
||||
const a = one || '';
|
||||
const b = other || '';
|
||||
@@ -30,14 +30,19 @@ export function compareFileNames(one: string, other: string): number {
|
||||
return result;
|
||||
}
|
||||
|
||||
return noIntlCompareFileNames(one, other);
|
||||
return noIntlCompareFileNames(one, other, caseSensitive);
|
||||
}
|
||||
|
||||
const FileNameMatch = /^(.*?)(\.([^.]*))?$/;
|
||||
|
||||
export function noIntlCompareFileNames(one: string, other: string): number {
|
||||
const [oneName, oneExtension] = extractNameAndExtension(one, true);
|
||||
const [otherName, otherExtension] = extractNameAndExtension(other, true);
|
||||
export function noIntlCompareFileNames(one: string, other: string, caseSensitive = false): number {
|
||||
if (!caseSensitive) {
|
||||
one = one && one.toLowerCase();
|
||||
other = other && other.toLowerCase();
|
||||
}
|
||||
|
||||
const [oneName, oneExtension] = extractNameAndExtension(one);
|
||||
const [otherName, otherExtension] = extractNameAndExtension(other);
|
||||
|
||||
if (oneName !== otherName) {
|
||||
return oneName < otherName ? -1 : 1;
|
||||
@@ -79,8 +84,8 @@ export function compareFileExtensions(one: string, other: string): number {
|
||||
}
|
||||
|
||||
function noIntlCompareFileExtensions(one: string, other: string): number {
|
||||
const [oneName, oneExtension] = extractNameAndExtension(one, true);
|
||||
const [otherName, otherExtension] = extractNameAndExtension(other, true);
|
||||
const [oneName, oneExtension] = extractNameAndExtension(one && one.toLowerCase());
|
||||
const [otherName, otherExtension] = extractNameAndExtension(other && other.toLowerCase());
|
||||
|
||||
if (oneExtension !== otherExtension) {
|
||||
return oneExtension < otherExtension ? -1 : 1;
|
||||
@@ -93,32 +98,49 @@ function noIntlCompareFileExtensions(one: string, other: string): number {
|
||||
return oneName < otherName ? -1 : 1;
|
||||
}
|
||||
|
||||
function extractNameAndExtension(str?: string, lowercase?: boolean): [string, string] {
|
||||
const match = str ? FileNameMatch.exec(lowercase ? str.toLowerCase() : str) : [] as RegExpExecArray;
|
||||
function extractNameAndExtension(str?: string): [string, string] {
|
||||
const match = str ? FileNameMatch.exec(str) : [] as RegExpExecArray;
|
||||
|
||||
return [(match && match[1]) || '', (match && match[3]) || ''];
|
||||
}
|
||||
|
||||
export function comparePaths(one: string, other: string): number {
|
||||
function comparePathComponents(one: string, other: string, caseSensitive = false): number {
|
||||
if (!caseSensitive) {
|
||||
one = one && one.toLowerCase();
|
||||
other = other && other.toLowerCase();
|
||||
}
|
||||
|
||||
if (one === other) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return one < other ? -1 : 1;
|
||||
}
|
||||
|
||||
export function comparePaths(one: string, other: string, caseSensitive = false): number {
|
||||
const oneParts = one.split(paths.nativeSep);
|
||||
const otherParts = other.split(paths.nativeSep);
|
||||
|
||||
const lastOne = oneParts.length - 1;
|
||||
const lastOther = otherParts.length - 1;
|
||||
let endOne: boolean, endOther: boolean, onePart: string, otherPart: string;
|
||||
let endOne: boolean, endOther: boolean;
|
||||
|
||||
for (let i = 0; ; i++) {
|
||||
endOne = lastOne === i;
|
||||
endOther = lastOther === i;
|
||||
|
||||
if (endOne && endOther) {
|
||||
return compareFileNames(oneParts[i], otherParts[i]);
|
||||
return compareFileNames(oneParts[i], otherParts[i], caseSensitive);
|
||||
} else if (endOne) {
|
||||
return -1;
|
||||
} else if (endOther) {
|
||||
return 1;
|
||||
} else if ((onePart = oneParts[i].toLowerCase()) !== (otherPart = otherParts[i].toLowerCase())) {
|
||||
return onePart < otherPart ? -1 : 1;
|
||||
}
|
||||
|
||||
const result = comparePathComponents(oneParts[i], otherParts[i], caseSensitive);
|
||||
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ export function register(what: string, fn: Function): (...args: any[]) => void {
|
||||
const thisArguments = allArgs.shift();
|
||||
fn.apply(fn, thisArguments);
|
||||
if (allArgs.length > 0) {
|
||||
Platform.setTimeout(doIt, 500);
|
||||
setTimeout(doIt, 500);
|
||||
}
|
||||
};
|
||||
doIt();
|
||||
|
||||
@@ -1,325 +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 { DiffChange } from 'vs/base/common/diff/diffChange';
|
||||
|
||||
export interface ISequence {
|
||||
getLength(): number;
|
||||
getElementHash(index: number): string;
|
||||
[index: number]: string;
|
||||
}
|
||||
|
||||
export interface IDiffChange {
|
||||
/**
|
||||
* The position of the first element in the original sequence which
|
||||
* this change affects.
|
||||
*/
|
||||
originalStart: number;
|
||||
|
||||
/**
|
||||
* The number of elements from the original sequence which were
|
||||
* affected.
|
||||
*/
|
||||
originalLength: number;
|
||||
|
||||
/**
|
||||
* The position of the first element in the modified sequence which
|
||||
* this change affects.
|
||||
*/
|
||||
modifiedStart: number;
|
||||
|
||||
/**
|
||||
* The number of elements from the modified sequence which were
|
||||
* affected (added).
|
||||
*/
|
||||
modifiedLength: number;
|
||||
}
|
||||
|
||||
export interface IContinueProcessingPredicate {
|
||||
(furthestOriginalIndex: number, originalSequence: ISequence, matchLengthOfLongest: number): boolean;
|
||||
}
|
||||
|
||||
export interface IHashFunction {
|
||||
(sequence: ISequence, index: number): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of the difference algorithm described by Hirschberg
|
||||
*/
|
||||
export class LcsDiff2 {
|
||||
|
||||
private x: ISequence;
|
||||
private y: ISequence;
|
||||
|
||||
private ids_for_x: number[];
|
||||
private ids_for_y: number[];
|
||||
|
||||
private resultX: boolean[];
|
||||
private resultY: boolean[];
|
||||
private forwardPrev: number[];
|
||||
private forwardCurr: number[];
|
||||
private backwardPrev: number[];
|
||||
private backwardCurr: number[];
|
||||
|
||||
constructor(originalSequence: ISequence, newSequence: ISequence, continueProcessingPredicate: IContinueProcessingPredicate, hashFunc: IHashFunction) {
|
||||
this.x = originalSequence;
|
||||
this.y = newSequence;
|
||||
this.ids_for_x = [];
|
||||
this.ids_for_y = [];
|
||||
|
||||
this.resultX = [];
|
||||
this.resultY = [];
|
||||
this.forwardPrev = [];
|
||||
this.forwardCurr = [];
|
||||
this.backwardPrev = [];
|
||||
this.backwardCurr = [];
|
||||
|
||||
for (let i = 0, length = this.x.getLength(); i < length; i++) {
|
||||
this.resultX[i] = false;
|
||||
}
|
||||
|
||||
for (let i = 0, length = this.y.getLength(); i <= length; i++) {
|
||||
this.resultY[i] = false;
|
||||
}
|
||||
|
||||
this.ComputeUniqueIdentifiers();
|
||||
}
|
||||
|
||||
private ComputeUniqueIdentifiers() {
|
||||
let xLength = this.x.getLength();
|
||||
let yLength = this.y.getLength();
|
||||
this.ids_for_x = new Array<number>(xLength);
|
||||
this.ids_for_y = new Array<number>(yLength);
|
||||
|
||||
// Create a new hash table for unique elements from the original
|
||||
// sequence.
|
||||
let hashTable: { [key: string]: number; } = {};
|
||||
let currentUniqueId = 1;
|
||||
let i: number;
|
||||
|
||||
// Fill up the hash table for unique elements
|
||||
for (i = 0; i < xLength; i++) {
|
||||
let xElementHash = this.x.getElementHash(i);
|
||||
if (!hashTable.hasOwnProperty(xElementHash)) {
|
||||
// No entry in the hashtable so this is a new unique element.
|
||||
// Assign the element a new unique identifier and add it to the
|
||||
// hash table
|
||||
this.ids_for_x[i] = currentUniqueId++;
|
||||
hashTable[xElementHash] = this.ids_for_x[i];
|
||||
} else {
|
||||
this.ids_for_x[i] = hashTable[xElementHash];
|
||||
}
|
||||
}
|
||||
|
||||
// Now match up modified elements
|
||||
for (i = 0; i < yLength; i++) {
|
||||
let yElementHash = this.y.getElementHash(i);
|
||||
if (!hashTable.hasOwnProperty(yElementHash)) {
|
||||
this.ids_for_y[i] = currentUniqueId++;
|
||||
hashTable[yElementHash] = this.ids_for_y[i];
|
||||
} else {
|
||||
this.ids_for_y[i] = hashTable[yElementHash];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ElementsAreEqual(xIndex: number, yIndex: number): boolean {
|
||||
return this.ids_for_x[xIndex] === this.ids_for_y[yIndex];
|
||||
}
|
||||
|
||||
public ComputeDiff(): IDiffChange[] {
|
||||
let xLength = this.x.getLength();
|
||||
let yLength = this.y.getLength();
|
||||
|
||||
this.execute(0, xLength - 1, 0, yLength - 1);
|
||||
|
||||
// Construct the changes
|
||||
let i = 0;
|
||||
let j = 0;
|
||||
let xChangeStart: number, yChangeStart: number;
|
||||
let changes: DiffChange[] = [];
|
||||
while (i < xLength && j < yLength) {
|
||||
if (this.resultX[i] && this.resultY[j]) {
|
||||
// No change
|
||||
i++;
|
||||
j++;
|
||||
} else {
|
||||
xChangeStart = i;
|
||||
yChangeStart = j;
|
||||
while (i < xLength && !this.resultX[i]) {
|
||||
i++;
|
||||
}
|
||||
while (j < yLength && !this.resultY[j]) {
|
||||
j++;
|
||||
}
|
||||
changes.push(new DiffChange(xChangeStart, i - xChangeStart, yChangeStart, j - yChangeStart));
|
||||
}
|
||||
}
|
||||
if (i < xLength) {
|
||||
changes.push(new DiffChange(i, xLength - i, yLength, 0));
|
||||
}
|
||||
if (j < yLength) {
|
||||
changes.push(new DiffChange(xLength, 0, j, yLength - j));
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
private forward(xStart: number, xStop: number, yStart: number, yStop: number): number[] {
|
||||
let prev = this.forwardPrev,
|
||||
curr = this.forwardCurr,
|
||||
tmp: number[],
|
||||
i: number,
|
||||
j: number;
|
||||
|
||||
// First line
|
||||
prev[yStart] = this.ElementsAreEqual(xStart, yStart) ? 1 : 0;
|
||||
for (j = yStart + 1; j <= yStop; j++) {
|
||||
prev[j] = this.ElementsAreEqual(xStart, j) ? 1 : prev[j - 1];
|
||||
}
|
||||
|
||||
for (i = xStart + 1; i <= xStop; i++) {
|
||||
// First column
|
||||
curr[yStart] = this.ElementsAreEqual(i, yStart) ? 1 : prev[yStart];
|
||||
|
||||
for (j = yStart + 1; j <= yStop; j++) {
|
||||
if (this.ElementsAreEqual(i, j)) {
|
||||
curr[j] = prev[j - 1] + 1;
|
||||
} else {
|
||||
curr[j] = prev[j] > curr[j - 1] ? prev[j] : curr[j - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Swap prev & curr
|
||||
tmp = curr;
|
||||
curr = prev;
|
||||
prev = tmp;
|
||||
}
|
||||
|
||||
// Result is always in prev
|
||||
return prev;
|
||||
}
|
||||
|
||||
private backward(xStart: number, xStop: number, yStart: number, yStop: number): number[] {
|
||||
let prev = this.backwardPrev,
|
||||
curr = this.backwardCurr,
|
||||
tmp: number[],
|
||||
i: number,
|
||||
j: number;
|
||||
|
||||
// Last line
|
||||
prev[yStop] = this.ElementsAreEqual(xStop, yStop) ? 1 : 0;
|
||||
for (j = yStop - 1; j >= yStart; j--) {
|
||||
prev[j] = this.ElementsAreEqual(xStop, j) ? 1 : prev[j + 1];
|
||||
}
|
||||
|
||||
for (i = xStop - 1; i >= xStart; i--) {
|
||||
// Last column
|
||||
curr[yStop] = this.ElementsAreEqual(i, yStop) ? 1 : prev[yStop];
|
||||
|
||||
for (j = yStop - 1; j >= yStart; j--) {
|
||||
if (this.ElementsAreEqual(i, j)) {
|
||||
curr[j] = prev[j + 1] + 1;
|
||||
} else {
|
||||
curr[j] = prev[j] > curr[j + 1] ? prev[j] : curr[j + 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Swap prev & curr
|
||||
tmp = curr;
|
||||
curr = prev;
|
||||
prev = tmp;
|
||||
}
|
||||
|
||||
// Result is always in prev
|
||||
return prev;
|
||||
}
|
||||
|
||||
private findCut(xStart: number, xStop: number, yStart: number, yStop: number, middle: number): number {
|
||||
let L1 = this.forward(xStart, middle, yStart, yStop);
|
||||
let L2 = this.backward(middle + 1, xStop, yStart, yStop);
|
||||
|
||||
// First cut
|
||||
let max = L2[yStart], cut = yStart - 1;
|
||||
|
||||
// Middle cut
|
||||
for (let j = yStart; j < yStop; j++) {
|
||||
if (L1[j] + L2[j + 1] > max) {
|
||||
max = L1[j] + L2[j + 1];
|
||||
cut = j;
|
||||
}
|
||||
}
|
||||
|
||||
// Last cut
|
||||
if (L1[yStop] > max) {
|
||||
max = L1[yStop];
|
||||
cut = yStop;
|
||||
}
|
||||
|
||||
return cut;
|
||||
}
|
||||
|
||||
private execute(xStart: number, xStop: number, yStart: number, yStop: number) {
|
||||
// Do some prefix trimming
|
||||
while (xStart <= xStop && yStart <= yStop && this.ElementsAreEqual(xStart, yStart)) {
|
||||
this.resultX[xStart] = true;
|
||||
xStart++;
|
||||
this.resultY[yStart] = true;
|
||||
yStart++;
|
||||
}
|
||||
|
||||
// Do some suffix trimming
|
||||
while (xStart <= xStop && yStart <= yStop && this.ElementsAreEqual(xStop, yStop)) {
|
||||
this.resultX[xStop] = true;
|
||||
xStop--;
|
||||
this.resultY[yStop] = true;
|
||||
yStop--;
|
||||
}
|
||||
|
||||
if (xStart > xStop || yStart > yStop) {
|
||||
return;
|
||||
}
|
||||
|
||||
let found: number, i: number;
|
||||
if (xStart === xStop) {
|
||||
found = -1;
|
||||
for (i = yStart; i <= yStop; i++) {
|
||||
if (this.ElementsAreEqual(xStart, i)) {
|
||||
found = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found >= 0) {
|
||||
this.resultX[xStart] = true;
|
||||
this.resultY[found] = true;
|
||||
}
|
||||
} else if (yStart === yStop) {
|
||||
found = -1;
|
||||
for (i = xStart; i <= xStop; i++) {
|
||||
if (this.ElementsAreEqual(i, yStart)) {
|
||||
found = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found >= 0) {
|
||||
this.resultX[found] = true;
|
||||
this.resultY[yStart] = true;
|
||||
}
|
||||
} else {
|
||||
let middle = Math.floor((xStart + xStop) / 2);
|
||||
let cut = this.findCut(xStart, xStop, yStart, yStop, middle);
|
||||
|
||||
if (yStart <= cut) {
|
||||
this.execute(xStart, middle, yStart, cut);
|
||||
}
|
||||
|
||||
if (cut + 1 <= yStop) {
|
||||
this.execute(middle + 1, xStop, cut + 1, yStop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,10 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import platform = require('vs/base/common/platform');
|
||||
import types = require('vs/base/common/types');
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { TPromise, IPromiseError, IPromiseErrorDetail } from 'vs/base/common/winjs.base';
|
||||
|
||||
// ------ BEGIN Hook up error listeners to winjs promises
|
||||
@@ -79,7 +76,7 @@ export class ErrorHandler {
|
||||
this.listeners = [];
|
||||
|
||||
this.unexpectedErrorHandler = function (e: any) {
|
||||
platform.setTimeout(() => {
|
||||
setTimeout(() => {
|
||||
if (e.stack) {
|
||||
throw new Error(e.message + '\n\n' + e.stack);
|
||||
}
|
||||
@@ -238,19 +235,22 @@ export function disposed(what: string): Error {
|
||||
}
|
||||
|
||||
export interface IErrorOptions {
|
||||
severity?: Severity;
|
||||
actions?: IAction[];
|
||||
}
|
||||
|
||||
export function create(message: string, options: IErrorOptions = {}): Error {
|
||||
let result = new Error(message);
|
||||
export interface IErrorWithActions {
|
||||
actions?: IAction[];
|
||||
}
|
||||
|
||||
if (types.isNumber(options.severity)) {
|
||||
(<any>result).severity = options.severity;
|
||||
}
|
||||
export function isErrorWithActions(obj: any): obj is IErrorWithActions {
|
||||
return obj instanceof Error && Array.isArray((obj as IErrorWithActions).actions);
|
||||
}
|
||||
|
||||
export function create(message: string, options: IErrorOptions = Object.create(null)): Error & IErrorWithActions {
|
||||
const result = new Error(message);
|
||||
|
||||
if (options.actions) {
|
||||
(<any>result).actions = options.actions;
|
||||
(<IErrorWithActions>result).actions = options.actions;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -341,6 +341,22 @@ export function matchesFuzzy(word: string, wordToMatchAgainst: string, enableSep
|
||||
return enableSeparateSubstringMatching ? fuzzySeparateFilter(word, wordToMatchAgainst) : fuzzyContiguousFilter(word, wordToMatchAgainst);
|
||||
}
|
||||
|
||||
export function skipScore(pattern: string, word: string, patternMaxWhitespaceIgnore?: number): [number, number[]] {
|
||||
pattern = pattern.toLowerCase();
|
||||
word = word.toLowerCase();
|
||||
|
||||
const matches: number[] = [];
|
||||
let idx = 0;
|
||||
for (let pos = 0; pos < pattern.length; ++pos) {
|
||||
const thisIdx = word.indexOf(pattern.charAt(pos), idx);
|
||||
if (thisIdx >= 0) {
|
||||
matches.push(thisIdx);
|
||||
idx = thisIdx + 1;
|
||||
}
|
||||
}
|
||||
return [matches.length, matches];
|
||||
}
|
||||
|
||||
//#region --- fuzzyScore ---
|
||||
|
||||
export function createMatches(position: number[]): IMatch[] {
|
||||
@@ -560,7 +576,7 @@ export function fuzzyScore(pattern: string, word: string, patternMaxWhitespaceIg
|
||||
_matchesCount = 0;
|
||||
_topScore = -100;
|
||||
_patternStartPos = patternStartPos;
|
||||
_findAllMatches(patternLen, wordLen, 0, new LazyArray(), false);
|
||||
_findAllMatches(patternLen, wordLen, patternLen === wordLen ? 1 : 0, new LazyArray(), false);
|
||||
|
||||
if (_matchesCount === 0) {
|
||||
return undefined;
|
||||
|
||||
@@ -220,8 +220,12 @@ function parseRegExp(pattern: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
// Tail: Add the slash we had split on if there is more to come and the next one is not a globstar
|
||||
if (index < segments.length - 1 && segments[index + 1] !== GLOBSTAR) {
|
||||
// Tail: Add the slash we had split on if there is more to come and the remaining pattern is not a globstar
|
||||
// For example if pattern: some/**/*.js we want the "/" after some to be included in the RegEx to prevent
|
||||
// a folder called "something" to match as well.
|
||||
// However, if pattern: some/**, we tolerate that we also match on "something" because our globstar behaviour
|
||||
// is to match 0-N segments.
|
||||
if (index < segments.length - 1 && (segments[index + 1] !== GLOBSTAR || index + 2 < segments.length)) {
|
||||
regEx += PATH_REGEX;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import URI from 'vs/base/common/uri';
|
||||
import platform = require('vs/base/common/platform');
|
||||
import { nativeSep, normalize, isEqualOrParent, isEqual, basename as pathsBasename, join } from 'vs/base/common/paths';
|
||||
import { endsWith, ltrim } from 'vs/base/common/strings';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
export interface IWorkspaceFolderProvider {
|
||||
getWorkspaceFolder(resource: URI): { uri: URI };
|
||||
@@ -29,8 +30,8 @@ export function getPathLabel(resource: URI | string, rootProvider?: IWorkspaceFo
|
||||
resource = URI.file(resource);
|
||||
}
|
||||
|
||||
if (resource.scheme !== 'file' && resource.scheme !== 'untitled') {
|
||||
return resource.authority + resource.path;
|
||||
if (resource.scheme !== Schemas.file && resource.scheme !== Schemas.untitled) {
|
||||
return resource.with({ query: null, fragment: null }).toString(true);
|
||||
}
|
||||
|
||||
// return early if we can resolve a relative path label from the root
|
||||
@@ -362,4 +363,4 @@ export function mnemonicButtonLabel(label: string): string {
|
||||
|
||||
export function unmnemonicLabel(label: string): string {
|
||||
return label.replace(/&/g, '&&');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
export function values<K, V>(map: Map<K, V>): V[] {
|
||||
export function values<V = any>(set: Set<V>): V[];
|
||||
export function values<K = any, V = any>(map: Map<K, V>): V[];
|
||||
export function values<V>(forEachable: { forEach(callback: (value: V, ...more: any[]) => any) }): V[] {
|
||||
const result: V[] = [];
|
||||
map.forEach(value => result.push(value));
|
||||
|
||||
forEachable.forEach(value => result.push(value));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
[{
|
||||
"name": "chjj-marked",
|
||||
"repositoryURL": "https://github.com/npmcomponent/chjj-marked",
|
||||
"version": "0.3.6",
|
||||
"version": "0.3.12",
|
||||
"license": "MIT"
|
||||
}]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* marked - a markdown parser
|
||||
* Copyright (c) 2011-2014, Christopher Jeffrey. (Source EULAd)
|
||||
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
|
||||
* https://github.com/chjj/marked
|
||||
*/
|
||||
|
||||
@@ -16,19 +16,26 @@ var block = {
|
||||
newline: /^\n+/,
|
||||
code: /^( {4}[^\n]+\n*)+/,
|
||||
fences: noop,
|
||||
hr: /^( *[-*_]){3,} *(?:\n+|$)/,
|
||||
hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,
|
||||
heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
|
||||
nptable: noop,
|
||||
lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,
|
||||
blockquote: /^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,
|
||||
blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,
|
||||
list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
|
||||
html: /^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,
|
||||
def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
|
||||
def: /^ {0,3}\[(label)\]: *\n? *<?([^\s>]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/,
|
||||
table: noop,
|
||||
paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
|
||||
lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,
|
||||
paragraph: /^([^\n]+(?:\n?(?!hr|heading|lheading| {0,3}>|tag)[^\n]+)+)/,
|
||||
text: /^[^\n]+/
|
||||
};
|
||||
|
||||
block._label = /(?:\\[\[\]]|[^\[\]])+/;
|
||||
block._title = /(?:"(?:\\"|[^"]|"[^"\n]*")*"|'\n?(?:[^'\n]+\n?)*'|\([^()]*\))/;
|
||||
block.def = replace(block.def)
|
||||
('label', block._label)
|
||||
('title', block._title)
|
||||
();
|
||||
|
||||
block.bullet = /(?:[*+-]|\d+\.)/;
|
||||
block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
|
||||
block.item = replace(block.item, 'gm')
|
||||
@@ -37,23 +44,19 @@ block.item = replace(block.item, 'gm')
|
||||
|
||||
block.list = replace(block.list)
|
||||
(/bull/g, block.bullet)
|
||||
('hr', '\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))')
|
||||
('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))')
|
||||
('def', '\\n+(?=' + block.def.source + ')')
|
||||
();
|
||||
|
||||
block.blockquote = replace(block.blockquote)
|
||||
('def', block.def)
|
||||
();
|
||||
|
||||
block._tag = '(?!(?:'
|
||||
+ 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'
|
||||
+ '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'
|
||||
+ '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b';
|
||||
+ '|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b';
|
||||
|
||||
block.html = replace(block.html)
|
||||
('comment', /<!--[\s\S]*?-->/)
|
||||
('closed', /<(tag)[\s\S]+?<\/\1>/)
|
||||
('closing', /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)
|
||||
('closing', /<tag(?:"[^"]*"|'[^']*'|\s[^'"\/>]*)*?\/?>/)
|
||||
(/tag/g, block._tag)
|
||||
();
|
||||
|
||||
@@ -61,9 +64,11 @@ block.paragraph = replace(block.paragraph)
|
||||
('hr', block.hr)
|
||||
('heading', block.heading)
|
||||
('lheading', block.lheading)
|
||||
('blockquote', block.blockquote)
|
||||
('tag', '<' + block._tag)
|
||||
('def', block.def)
|
||||
();
|
||||
|
||||
block.blockquote = replace(block.blockquote)
|
||||
('paragraph', block.paragraph)
|
||||
();
|
||||
|
||||
/**
|
||||
@@ -77,15 +82,15 @@ block.normal = merge({}, block);
|
||||
*/
|
||||
|
||||
block.gfm = merge({}, block.normal, {
|
||||
fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,
|
||||
fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\n? *\1 *(?:\n+|$)/,
|
||||
paragraph: /^/,
|
||||
heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/
|
||||
});
|
||||
|
||||
block.gfm.paragraph = replace(block.paragraph)
|
||||
('(?!', '(?!'
|
||||
+ block.gfm.fences.source.replace('\\1', '\\2') + '|'
|
||||
+ block.list.source.replace('\\1', '\\3') + '|')
|
||||
+ block.gfm.fences.source.replace('\\1', '\\2') + '|'
|
||||
+ block.list.source.replace('\\1', '\\3') + '|')
|
||||
();
|
||||
|
||||
/**
|
||||
@@ -126,7 +131,7 @@ Lexer.rules = block;
|
||||
* Static Lex Method
|
||||
*/
|
||||
|
||||
Lexer.lex = function(src, options) {
|
||||
Lexer.lex = function (src, options) {
|
||||
var lexer = new Lexer(options);
|
||||
return lexer.lex(src);
|
||||
};
|
||||
@@ -135,7 +140,7 @@ Lexer.lex = function(src, options) {
|
||||
* Preprocessing
|
||||
*/
|
||||
|
||||
Lexer.prototype.lex = function(src) {
|
||||
Lexer.prototype.lex = function (src) {
|
||||
src = src
|
||||
.replace(/\r\n|\r/g, '\n')
|
||||
.replace(/\t/g, ' ')
|
||||
@@ -149,7 +154,7 @@ Lexer.prototype.lex = function(src) {
|
||||
* Lexing
|
||||
*/
|
||||
|
||||
Lexer.prototype.token = function(src, top, bq) {
|
||||
Lexer.prototype.token = function (src, top) {
|
||||
var src = src.replace(/^ +$/gm, '')
|
||||
, next
|
||||
, loose
|
||||
@@ -159,6 +164,7 @@ Lexer.prototype.token = function(src, top, bq) {
|
||||
, item
|
||||
, space
|
||||
, i
|
||||
, tag
|
||||
, l;
|
||||
|
||||
while (src) {
|
||||
@@ -239,17 +245,6 @@ Lexer.prototype.token = function(src, top, bq) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// lheading
|
||||
if (cap = this.rules.lheading.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
this.tokens.push({
|
||||
type: 'heading',
|
||||
depth: cap[2] === '=' ? 1 : 2,
|
||||
text: cap[1]
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// hr
|
||||
if (cap = this.rules.hr.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
@@ -272,7 +267,7 @@ Lexer.prototype.token = function(src, top, bq) {
|
||||
// Pass `top` to keep the current
|
||||
// "toplevel" state. This is exactly
|
||||
// how markdown.pl works.
|
||||
this.token(cap, top, true);
|
||||
this.token(cap, top);
|
||||
|
||||
this.tokens.push({
|
||||
type: 'blockquote_end'
|
||||
@@ -341,7 +336,7 @@ Lexer.prototype.token = function(src, top, bq) {
|
||||
});
|
||||
|
||||
// Recurse.
|
||||
this.token(item, false, bq);
|
||||
this.token(item, false);
|
||||
|
||||
this.tokens.push({
|
||||
type: 'list_item_end'
|
||||
@@ -370,12 +365,16 @@ Lexer.prototype.token = function(src, top, bq) {
|
||||
}
|
||||
|
||||
// def
|
||||
if ((!bq && top) && (cap = this.rules.def.exec(src))) {
|
||||
if (top && (cap = this.rules.def.exec(src))) {
|
||||
src = src.substring(cap[0].length);
|
||||
this.tokens.links[cap[1].toLowerCase()] = {
|
||||
href: cap[2],
|
||||
title: cap[3]
|
||||
};
|
||||
if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1);
|
||||
tag = cap[1].toLowerCase();
|
||||
if (!this.tokens.links[tag]) {
|
||||
this.tokens.links[tag] = {
|
||||
href: cap[2],
|
||||
title: cap[3]
|
||||
};
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -413,6 +412,17 @@ Lexer.prototype.token = function(src, top, bq) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// lheading
|
||||
if (cap = this.rules.lheading.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
this.tokens.push({
|
||||
type: 'heading',
|
||||
depth: cap[2] === '=' ? 1 : 2,
|
||||
text: cap[1]
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// top-level paragraph
|
||||
if (top && (cap = this.rules.paragraph.exec(src))) {
|
||||
src = src.substring(cap[0].length);
|
||||
@@ -451,21 +461,29 @@ Lexer.prototype.token = function(src, top, bq) {
|
||||
|
||||
var inline = {
|
||||
escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
|
||||
autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
|
||||
autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/,
|
||||
url: noop,
|
||||
tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
|
||||
tag: /^<!--[\s\S]*?-->|^<\/?[a-zA-Z0-9\-]+(?:"[^"]*"|'[^']*'|\s[^<'">\/]*)*?\/?>/,
|
||||
link: /^!?\[(inside)\]\(href\)/,
|
||||
reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
|
||||
nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
|
||||
nolink: /^!?\[((?:\[[^\]]*\]|\\[\[\]]|[^\[\]])*)\]/,
|
||||
strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
|
||||
em: /^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
|
||||
code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,
|
||||
em: /^_([^\s_](?:[^_]|__)+?[^\s_])_\b|^\*((?:\*\*|[^*])+?)\*(?!\*)/,
|
||||
code: /^(`+)(\s*)([\s\S]*?[^`]?)\2\1(?!`)/,
|
||||
br: /^ {2,}\n(?!\s*$)/,
|
||||
del: noop,
|
||||
text: /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/
|
||||
text: /^[\s\S]+?(?=[\\<!\[`*]|\b_| {2,}\n|$)/
|
||||
};
|
||||
|
||||
inline._inside = /(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/;
|
||||
inline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/;
|
||||
inline._email = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/;
|
||||
|
||||
inline.autolink = replace(inline.autolink)
|
||||
('scheme', inline._scheme)
|
||||
('email', inline._email)
|
||||
()
|
||||
|
||||
inline._inside = /(?:\[[^\]]*\]|\\[\[\]]|[^\[\]]|\](?=[^\[]*\]))*/;
|
||||
inline._href = /\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;
|
||||
|
||||
inline.link = replace(inline.link)
|
||||
@@ -498,11 +516,14 @@ inline.pedantic = merge({}, inline.normal, {
|
||||
|
||||
inline.gfm = merge({}, inline.normal, {
|
||||
escape: replace(inline.escape)('])', '~|])')(),
|
||||
url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
|
||||
url: replace(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/)
|
||||
('email', inline._email)
|
||||
(),
|
||||
_backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,
|
||||
del: /^~~(?=\S)([\s\S]*?\S)~~/,
|
||||
text: replace(inline.text)
|
||||
(']|', '~]|')
|
||||
('|', '|https?://|')
|
||||
('|', '|https?://|ftp://|www\\.|[a-zA-Z0-9.!#$%&\'*+/=?^_`{\\|}~-]+@|')
|
||||
()
|
||||
});
|
||||
|
||||
@@ -552,7 +573,7 @@ InlineLexer.rules = inline;
|
||||
* Static Lexing/Compiling Method
|
||||
*/
|
||||
|
||||
InlineLexer.output = function(src, links, options) {
|
||||
InlineLexer.output = function (src, links, options) {
|
||||
var inline = new InlineLexer(links, options);
|
||||
return inline.output(src);
|
||||
};
|
||||
@@ -561,7 +582,7 @@ InlineLexer.output = function(src, links, options) {
|
||||
* Lexing/Compiling
|
||||
*/
|
||||
|
||||
InlineLexer.prototype.output = function(src) {
|
||||
InlineLexer.prototype.output = function (src) {
|
||||
var out = ''
|
||||
, link
|
||||
, text
|
||||
@@ -580,10 +601,8 @@ InlineLexer.prototype.output = function(src) {
|
||||
if (cap = this.rules.autolink.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
if (cap[2] === '@') {
|
||||
text = cap[1].charAt(6) === ':'
|
||||
? this.mangle(cap[1].substring(7))
|
||||
: this.mangle(cap[1]);
|
||||
href = this.mangle('mailto:') + text;
|
||||
text = escape(this.mangle(cap[1]));
|
||||
href = 'mailto:' + text;
|
||||
} else {
|
||||
text = escape(cap[1]);
|
||||
href = text;
|
||||
@@ -594,9 +613,19 @@ InlineLexer.prototype.output = function(src) {
|
||||
|
||||
// url (gfm)
|
||||
if (!this.inLink && (cap = this.rules.url.exec(src))) {
|
||||
cap[0] = this.rules._backpedal.exec(cap[0])[0];
|
||||
src = src.substring(cap[0].length);
|
||||
text = escape(cap[1]);
|
||||
href = text;
|
||||
if (cap[2] === '@') {
|
||||
text = escape(cap[0]);
|
||||
href = 'mailto:' + text;
|
||||
} else {
|
||||
text = escape(cap[0]);
|
||||
if (cap[1] === 'www.') {
|
||||
href = 'http://' + text;
|
||||
} else {
|
||||
href = text;
|
||||
}
|
||||
}
|
||||
out += this.renderer.link(href, null, text);
|
||||
continue;
|
||||
}
|
||||
@@ -631,7 +660,7 @@ InlineLexer.prototype.output = function(src) {
|
||||
|
||||
// reflink, nolink
|
||||
if ((cap = this.rules.reflink.exec(src))
|
||||
|| (cap = this.rules.nolink.exec(src))) {
|
||||
|| (cap = this.rules.nolink.exec(src))) {
|
||||
src = src.substring(cap[0].length);
|
||||
link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
|
||||
link = this.links[link.toLowerCase()];
|
||||
@@ -663,7 +692,7 @@ InlineLexer.prototype.output = function(src) {
|
||||
// code
|
||||
if (cap = this.rules.code.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
out += this.renderer.codespan(escape(cap[2], true));
|
||||
out += this.renderer.codespan(escape(cap[3].trim(), true));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -701,7 +730,7 @@ InlineLexer.prototype.output = function(src) {
|
||||
* Compile Link
|
||||
*/
|
||||
|
||||
InlineLexer.prototype.outputLink = function(cap, link) {
|
||||
InlineLexer.prototype.outputLink = function (cap, link) {
|
||||
var href = escape(link.href)
|
||||
, title = link.title ? escape(link.title) : null;
|
||||
|
||||
@@ -714,7 +743,7 @@ InlineLexer.prototype.outputLink = function(cap, link) {
|
||||
* Smartypants Transformations
|
||||
*/
|
||||
|
||||
InlineLexer.prototype.smartypants = function(text) {
|
||||
InlineLexer.prototype.smartypants = function (text) {
|
||||
if (!this.options.smartypants) return text;
|
||||
return text
|
||||
// em-dashes
|
||||
@@ -737,7 +766,7 @@ InlineLexer.prototype.smartypants = function(text) {
|
||||
* Mangle Links
|
||||
*/
|
||||
|
||||
InlineLexer.prototype.mangle = function(text) {
|
||||
InlineLexer.prototype.mangle = function (text) {
|
||||
if (!this.options.mangle) return text;
|
||||
var out = ''
|
||||
, l = text.length
|
||||
@@ -763,7 +792,7 @@ function Renderer(options) {
|
||||
this.options = options || {};
|
||||
}
|
||||
|
||||
Renderer.prototype.code = function(code, lang, escaped) {
|
||||
Renderer.prototype.code = function (code, lang, escaped) {
|
||||
if (this.options.highlight) {
|
||||
var out = this.options.highlight(code, lang);
|
||||
if (out != null && out !== code) {
|
||||
@@ -786,15 +815,15 @@ Renderer.prototype.code = function(code, lang, escaped) {
|
||||
+ '\n</code></pre>\n';
|
||||
};
|
||||
|
||||
Renderer.prototype.blockquote = function(quote) {
|
||||
Renderer.prototype.blockquote = function (quote) {
|
||||
return '<blockquote>\n' + quote + '</blockquote>\n';
|
||||
};
|
||||
|
||||
Renderer.prototype.html = function(html) {
|
||||
Renderer.prototype.html = function (html) {
|
||||
return html;
|
||||
};
|
||||
|
||||
Renderer.prototype.heading = function(text, level, raw) {
|
||||
Renderer.prototype.heading = function (text, level, raw) {
|
||||
return '<h'
|
||||
+ level
|
||||
+ ' id="'
|
||||
@@ -807,24 +836,24 @@ Renderer.prototype.heading = function(text, level, raw) {
|
||||
+ '>\n';
|
||||
};
|
||||
|
||||
Renderer.prototype.hr = function() {
|
||||
Renderer.prototype.hr = function () {
|
||||
return this.options.xhtml ? '<hr/>\n' : '<hr>\n';
|
||||
};
|
||||
|
||||
Renderer.prototype.list = function(body, ordered) {
|
||||
Renderer.prototype.list = function (body, ordered) {
|
||||
var type = ordered ? 'ol' : 'ul';
|
||||
return '<' + type + '>\n' + body + '</' + type + '>\n';
|
||||
};
|
||||
|
||||
Renderer.prototype.listitem = function(text) {
|
||||
Renderer.prototype.listitem = function (text) {
|
||||
return '<li>' + text + '</li>\n';
|
||||
};
|
||||
|
||||
Renderer.prototype.paragraph = function(text) {
|
||||
Renderer.prototype.paragraph = function (text) {
|
||||
return '<p>' + text + '</p>\n';
|
||||
};
|
||||
|
||||
Renderer.prototype.table = function(header, body) {
|
||||
Renderer.prototype.table = function (header, body) {
|
||||
return '<table>\n'
|
||||
+ '<thead>\n'
|
||||
+ header
|
||||
@@ -835,11 +864,11 @@ Renderer.prototype.table = function(header, body) {
|
||||
+ '</table>\n';
|
||||
};
|
||||
|
||||
Renderer.prototype.tablerow = function(content) {
|
||||
Renderer.prototype.tablerow = function (content) {
|
||||
return '<tr>\n' + content + '</tr>\n';
|
||||
};
|
||||
|
||||
Renderer.prototype.tablecell = function(content, flags) {
|
||||
Renderer.prototype.tablecell = function (content, flags) {
|
||||
var type = flags.header ? 'th' : 'td';
|
||||
var tag = flags.align
|
||||
? '<' + type + ' style="text-align:' + flags.align + '">'
|
||||
@@ -848,39 +877,42 @@ Renderer.prototype.tablecell = function(content, flags) {
|
||||
};
|
||||
|
||||
// span level renderer
|
||||
Renderer.prototype.strong = function(text) {
|
||||
Renderer.prototype.strong = function (text) {
|
||||
return '<strong>' + text + '</strong>';
|
||||
};
|
||||
|
||||
Renderer.prototype.em = function(text) {
|
||||
Renderer.prototype.em = function (text) {
|
||||
return '<em>' + text + '</em>';
|
||||
};
|
||||
|
||||
Renderer.prototype.codespan = function(text) {
|
||||
Renderer.prototype.codespan = function (text) {
|
||||
return '<code>' + text + '</code>';
|
||||
};
|
||||
|
||||
Renderer.prototype.br = function() {
|
||||
Renderer.prototype.br = function () {
|
||||
return this.options.xhtml ? '<br/>' : '<br>';
|
||||
};
|
||||
|
||||
Renderer.prototype.del = function(text) {
|
||||
Renderer.prototype.del = function (text) {
|
||||
return '<del>' + text + '</del>';
|
||||
};
|
||||
|
||||
Renderer.prototype.link = function(href, title, text) {
|
||||
Renderer.prototype.link = function (href, title, text) {
|
||||
if (this.options.sanitize) {
|
||||
try {
|
||||
var prot = decodeURIComponent(unescape(href))
|
||||
.replace(/[^\w:]/g, '')
|
||||
.toLowerCase();
|
||||
} catch (e) {
|
||||
return '';
|
||||
return text;
|
||||
}
|
||||
if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) {
|
||||
return '';
|
||||
return text;
|
||||
}
|
||||
}
|
||||
if (this.options.baseUrl && !originIndependentUrl.test(href)) {
|
||||
href = resolveUrl(this.options.baseUrl, href);
|
||||
}
|
||||
var out = '<a href="' + href + '"';
|
||||
if (title) {
|
||||
out += ' title="' + title + '"';
|
||||
@@ -889,7 +921,10 @@ Renderer.prototype.link = function(href, title, text) {
|
||||
return out;
|
||||
};
|
||||
|
||||
Renderer.prototype.image = function(href, title, text) {
|
||||
Renderer.prototype.image = function (href, title, text) {
|
||||
if (this.options.baseUrl && !originIndependentUrl.test(href)) {
|
||||
href = resolveUrl(this.options.baseUrl, href);
|
||||
}
|
||||
var out = '<img src="' + href + '" alt="' + text + '"';
|
||||
if (title) {
|
||||
out += ' title="' + title + '"';
|
||||
@@ -898,10 +933,36 @@ Renderer.prototype.image = function(href, title, text) {
|
||||
return out;
|
||||
};
|
||||
|
||||
Renderer.prototype.text = function(text) {
|
||||
Renderer.prototype.text = function (text) {
|
||||
return text;
|
||||
};
|
||||
|
||||
/**
|
||||
* TextRenderer
|
||||
* returns only the textual part of the token
|
||||
*/
|
||||
|
||||
function TextRenderer() { }
|
||||
|
||||
// no need for block level renderers
|
||||
|
||||
TextRenderer.prototype.strong =
|
||||
TextRenderer.prototype.em =
|
||||
TextRenderer.prototype.codespan =
|
||||
TextRenderer.prototype.del =
|
||||
TextRenderer.prototype.text = function (text) {
|
||||
return text;
|
||||
}
|
||||
|
||||
TextRenderer.prototype.link =
|
||||
TextRenderer.prototype.image = function (href, title, text) {
|
||||
return '' + text;
|
||||
}
|
||||
|
||||
TextRenderer.prototype.br = function () {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsing & Compiling
|
||||
*/
|
||||
@@ -919,8 +980,8 @@ function Parser(options) {
|
||||
* Static Parse Method
|
||||
*/
|
||||
|
||||
Parser.parse = function(src, options, renderer) {
|
||||
var parser = new Parser(options, renderer);
|
||||
Parser.parse = function (src, options) {
|
||||
var parser = new Parser(options);
|
||||
return parser.parse(src);
|
||||
};
|
||||
|
||||
@@ -928,8 +989,10 @@ Parser.parse = function(src, options, renderer) {
|
||||
* Parse Loop
|
||||
*/
|
||||
|
||||
Parser.prototype.parse = function(src) {
|
||||
this.inline = new InlineLexer(src.links, this.options, this.renderer);
|
||||
Parser.prototype.parse = function (src) {
|
||||
this.inline = new InlineLexer(src.links, this.options);
|
||||
// use an InlineLexer with a TextRenderer to extract pure text
|
||||
this.inlineText = new InlineLexer(src.links, merge({}, this.options, { renderer: new TextRenderer }));
|
||||
this.tokens = src.reverse();
|
||||
|
||||
var out = '';
|
||||
@@ -944,7 +1007,7 @@ Parser.prototype.parse = function(src) {
|
||||
* Next Token
|
||||
*/
|
||||
|
||||
Parser.prototype.next = function() {
|
||||
Parser.prototype.next = function () {
|
||||
return this.token = this.tokens.pop();
|
||||
};
|
||||
|
||||
@@ -952,7 +1015,7 @@ Parser.prototype.next = function() {
|
||||
* Preview Next Token
|
||||
*/
|
||||
|
||||
Parser.prototype.peek = function() {
|
||||
Parser.prototype.peek = function () {
|
||||
return this.tokens[this.tokens.length - 1] || 0;
|
||||
};
|
||||
|
||||
@@ -960,7 +1023,7 @@ Parser.prototype.peek = function() {
|
||||
* Parse Text Tokens
|
||||
*/
|
||||
|
||||
Parser.prototype.parseText = function() {
|
||||
Parser.prototype.parseText = function () {
|
||||
var body = this.token.text;
|
||||
|
||||
while (this.peek().type === 'text') {
|
||||
@@ -974,7 +1037,7 @@ Parser.prototype.parseText = function() {
|
||||
* Parse Current Token
|
||||
*/
|
||||
|
||||
Parser.prototype.tok = function() {
|
||||
Parser.prototype.tok = function () {
|
||||
switch (this.token.type) {
|
||||
case 'space': {
|
||||
return '';
|
||||
@@ -986,7 +1049,7 @@ Parser.prototype.tok = function() {
|
||||
return this.renderer.heading(
|
||||
this.inline.output(this.token.text),
|
||||
this.token.depth,
|
||||
this.token.text);
|
||||
unescape(this.inlineText.output(this.token.text)));
|
||||
}
|
||||
case 'code': {
|
||||
return this.renderer.code(this.token.text,
|
||||
@@ -999,13 +1062,11 @@ Parser.prototype.tok = function() {
|
||||
, i
|
||||
, row
|
||||
, cell
|
||||
, flags
|
||||
, j;
|
||||
|
||||
// header
|
||||
cell = '';
|
||||
for (i = 0; i < this.token.header.length; i++) {
|
||||
flags = { header: true, align: this.token.align[i] };
|
||||
cell += this.renderer.tablecell(
|
||||
this.inline.output(this.token.header[i]),
|
||||
{ header: true, align: this.token.align[i] }
|
||||
@@ -1096,8 +1157,8 @@ function escape(html, encode) {
|
||||
}
|
||||
|
||||
function unescape(html) {
|
||||
// explicitly match decimal, hex, and named HTML entities
|
||||
return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/g, function(_, n) {
|
||||
// explicitly match decimal, hex, and named HTML entities
|
||||
return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, function (_, n) {
|
||||
n = n.toLowerCase();
|
||||
if (n === 'colon') return ':';
|
||||
if (n.charAt(0) === '#') {
|
||||
@@ -1121,7 +1182,31 @@ function replace(regex, opt) {
|
||||
};
|
||||
}
|
||||
|
||||
function noop() {}
|
||||
function resolveUrl(base, href) {
|
||||
if (!baseUrls[' ' + base]) {
|
||||
// we can ignore everything in base after the last slash of its path component,
|
||||
// but we might need to add _that_
|
||||
// https://tools.ietf.org/html/rfc3986#section-3
|
||||
if (/^[^:]+:\/*[^/]*$/.test(base)) {
|
||||
baseUrls[' ' + base] = base + '/';
|
||||
} else {
|
||||
baseUrls[' ' + base] = base.replace(/[^/]*$/, '');
|
||||
}
|
||||
}
|
||||
base = baseUrls[' ' + base];
|
||||
|
||||
if (href.slice(0, 2) === '//') {
|
||||
return base.replace(/:[\s\S]*/, ':') + href;
|
||||
} else if (href.charAt(0) === '/') {
|
||||
return base.replace(/(:\/*[^/]*)[\s\S]*/, '$1') + href;
|
||||
} else {
|
||||
return base + href;
|
||||
}
|
||||
}
|
||||
var baseUrls = {};
|
||||
var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;
|
||||
|
||||
function noop() { }
|
||||
noop.exec = noop;
|
||||
|
||||
function merge(obj) {
|
||||
@@ -1147,6 +1232,14 @@ function merge(obj) {
|
||||
*/
|
||||
|
||||
function marked(src, opt, callback) {
|
||||
// throw error in case of non string input
|
||||
if (typeof src == 'undefined' || src === null)
|
||||
throw new Error('marked(): input parameter is undefined or null');
|
||||
if (typeof src != 'string')
|
||||
throw new Error('marked(): input parameter is of type ' +
|
||||
Object.prototype.toString.call(src) + ', string expected');
|
||||
|
||||
|
||||
if (callback || typeof opt === 'function') {
|
||||
if (!callback) {
|
||||
callback = opt;
|
||||
@@ -1168,7 +1261,7 @@ function marked(src, opt, callback) {
|
||||
|
||||
pending = tokens.length;
|
||||
|
||||
var done = function(err) {
|
||||
var done = function (err) {
|
||||
if (err) {
|
||||
opt.highlight = highlight;
|
||||
return callback(err);
|
||||
@@ -1198,11 +1291,11 @@ function marked(src, opt, callback) {
|
||||
if (!pending) return done();
|
||||
|
||||
for (; i < tokens.length; i++) {
|
||||
(function(token) {
|
||||
(function (token) {
|
||||
if (token.type !== 'code') {
|
||||
return --pending || done();
|
||||
}
|
||||
return highlight(token.text, token.lang, function(err, code) {
|
||||
return highlight(token.text, token.lang, function (err, code) {
|
||||
if (err) return done(err);
|
||||
if (code == null || code === token.text) {
|
||||
return --pending || done();
|
||||
@@ -1235,10 +1328,10 @@ function marked(src, opt, callback) {
|
||||
*/
|
||||
|
||||
marked.options =
|
||||
marked.setOptions = function(opt) {
|
||||
merge(marked.defaults, opt);
|
||||
return marked;
|
||||
};
|
||||
marked.setOptions = function (opt) {
|
||||
merge(marked.defaults, opt);
|
||||
return marked;
|
||||
};
|
||||
|
||||
marked.defaults = {
|
||||
gfm: true,
|
||||
@@ -1255,7 +1348,8 @@ marked.defaults = {
|
||||
smartypants: false,
|
||||
headerPrefix: '',
|
||||
renderer: new Renderer,
|
||||
xhtml: false
|
||||
xhtml: false,
|
||||
baseUrl: null
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1266,6 +1360,7 @@ marked.Parser = Parser;
|
||||
marked.parser = Parser.parse;
|
||||
|
||||
marked.Renderer = Renderer;
|
||||
marked.TextRenderer = TextRenderer;
|
||||
|
||||
marked.Lexer = Lexer;
|
||||
marked.lexer = Lexer.lex;
|
||||
|
||||
@@ -32,7 +32,7 @@ function replacer(key: string, value: any): any {
|
||||
return value;
|
||||
}
|
||||
|
||||
function revive(obj: any, depth: number): any {
|
||||
export function revive(obj: any, depth: number): any {
|
||||
|
||||
if (!obj || depth > 200) {
|
||||
return obj;
|
||||
@@ -55,4 +55,3 @@ function revive(obj: any, depth: number): any {
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
import types = require('vs/base/common/types');
|
||||
|
||||
export function clamp(value: number, min: number, max: number): number {
|
||||
@@ -18,6 +19,7 @@ export function rot(index: number, modulo: number): number {
|
||||
// {{SQL CARBON EDIT}}
|
||||
export type NumberCallback = (index: number) => void;
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
export function count(to: number, callback: NumberCallback): void;
|
||||
export function count(from: number, to: number, callback: NumberCallback): void;
|
||||
export function count(fromOrTo: number, toOrCallback?: NumberCallback | number, callback?: NumberCallback): any {
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { matchesFuzzy, IMatch } from 'vs/base/common/filters';
|
||||
import { ltrim } from 'vs/base/common/strings';
|
||||
|
||||
const octiconStartMarker = '$(';
|
||||
|
||||
export interface IParsedOcticons {
|
||||
text: string;
|
||||
octiconOffsets?: number[];
|
||||
}
|
||||
|
||||
export function parseOcticons(text: string): IParsedOcticons {
|
||||
const firstOcticonIndex = text.indexOf(octiconStartMarker);
|
||||
if (firstOcticonIndex === -1) {
|
||||
return { text }; // return early if the word does not include an octicon
|
||||
}
|
||||
|
||||
return doParseOcticons(text, firstOcticonIndex);
|
||||
}
|
||||
|
||||
function doParseOcticons(text: string, firstOcticonIndex: number): IParsedOcticons {
|
||||
const octiconOffsets: number[] = [];
|
||||
let textWithoutOcticons: string = '';
|
||||
|
||||
function appendChars(chars: string) {
|
||||
if (chars) {
|
||||
textWithoutOcticons += chars;
|
||||
|
||||
for (let i = 0; i < chars.length; i++) {
|
||||
octiconOffsets.push(octiconsOffset); // make sure to fill in octicon offsets
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let currentOcticonStart = -1;
|
||||
let currentOcticonValue: string = '';
|
||||
let octiconsOffset = 0;
|
||||
|
||||
let char: string;
|
||||
let nextChar: string;
|
||||
|
||||
let offset = firstOcticonIndex;
|
||||
const length = text.length;
|
||||
|
||||
// Append all characters until the first octicon
|
||||
appendChars(text.substr(0, firstOcticonIndex));
|
||||
|
||||
// example: $(file-symlink-file) my cool $(other-octicon) entry
|
||||
while (offset < length) {
|
||||
char = text[offset];
|
||||
nextChar = text[offset + 1];
|
||||
|
||||
// beginning of octicon: some value $( <--
|
||||
if (char === octiconStartMarker[0] && nextChar === octiconStartMarker[1]) {
|
||||
currentOcticonStart = offset;
|
||||
|
||||
// if we had a previous potential octicon value without
|
||||
// the closing ')', it was actually not an octicon and
|
||||
// so we have to add it to the actual value
|
||||
appendChars(currentOcticonValue);
|
||||
|
||||
currentOcticonValue = octiconStartMarker;
|
||||
|
||||
offset++; // jump over '('
|
||||
}
|
||||
|
||||
// end of octicon: some value $(some-octicon) <--
|
||||
else if (char === ')' && currentOcticonStart !== -1) {
|
||||
const currentOcticonLength = offset - currentOcticonStart + 1; // +1 to include the closing ')'
|
||||
octiconsOffset += currentOcticonLength;
|
||||
currentOcticonStart = -1;
|
||||
currentOcticonValue = '';
|
||||
}
|
||||
|
||||
// within octicon
|
||||
else if (currentOcticonStart !== -1) {
|
||||
currentOcticonValue += char;
|
||||
}
|
||||
|
||||
// any value outside of octicons
|
||||
else {
|
||||
appendChars(char);
|
||||
}
|
||||
|
||||
offset++;
|
||||
}
|
||||
|
||||
// if we had a previous potential octicon value without
|
||||
// the closing ')', it was actually not an octicon and
|
||||
// so we have to add it to the actual value
|
||||
appendChars(currentOcticonValue);
|
||||
|
||||
return { text: textWithoutOcticons, octiconOffsets };
|
||||
}
|
||||
|
||||
export function matchesFuzzyOcticonAware(query: string, target: IParsedOcticons, enableSeparateSubstringMatching = false): IMatch[] {
|
||||
const { text, octiconOffsets } = target;
|
||||
|
||||
// Return early if there are no octicon markers in the word to match against
|
||||
if (!octiconOffsets || octiconOffsets.length === 0) {
|
||||
return matchesFuzzy(query, text, enableSeparateSubstringMatching);
|
||||
}
|
||||
|
||||
// Trim the word to match against because it could have leading
|
||||
// whitespace now if the word started with an octicon
|
||||
const wordToMatchAgainstWithoutOcticonsTrimmed = ltrim(text, ' ');
|
||||
const leadingWhitespaceOffset = text.length - wordToMatchAgainstWithoutOcticonsTrimmed.length;
|
||||
|
||||
// match on value without octicons
|
||||
const matches = matchesFuzzy(query, wordToMatchAgainstWithoutOcticonsTrimmed, enableSeparateSubstringMatching);
|
||||
|
||||
// Map matches back to offsets with octicons and trimming
|
||||
if (matches) {
|
||||
for (let i = 0; i < matches.length; i++) {
|
||||
const octiconOffset = octiconOffsets[matches[i].start] /* octicon offsets at index */ + leadingWhitespaceOffset /* overall leading whitespace offset */;
|
||||
matches[i].start += octiconOffset;
|
||||
matches[i].end += octiconOffset;
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
Vendored
+5
-1
@@ -11,7 +11,8 @@ export interface PerformanceEntry {
|
||||
}
|
||||
|
||||
export function mark(name: string): void;
|
||||
export function measure(name: string, from?: string, to?: string): void;
|
||||
|
||||
export function measure(name: string, from?: string, to?: string): PerformanceEntry;
|
||||
|
||||
/**
|
||||
* Time something, shorthant for `mark` and `measure`
|
||||
@@ -23,6 +24,9 @@ export function time(name: string): { stop(): void };
|
||||
*/
|
||||
export function getEntries(type: 'mark' | 'measure'): PerformanceEntry[];
|
||||
|
||||
export function getEntry(type: 'mark' | 'measure', name: string): PerformanceEntry;
|
||||
|
||||
export function getDuration(from: string, to: string): number;
|
||||
|
||||
type ExportData = any[];
|
||||
export function importEntries(data: ExportData): void;
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
// 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
|
||||
@@ -23,6 +22,12 @@ if (typeof define !== "function" && typeof module === "object" && typeof module.
|
||||
|
||||
define([], function () {
|
||||
|
||||
var _global = this;
|
||||
if (typeof global !== 'undefined') {
|
||||
_global = global;
|
||||
}
|
||||
_global._performanceEntries = _global._performanceEntries || [];
|
||||
|
||||
// const _now = global.performance && performance.now ? performance.now : Date.now
|
||||
const _now = Date.now;
|
||||
|
||||
@@ -31,14 +36,14 @@ define([], function () {
|
||||
}
|
||||
|
||||
function exportEntries() {
|
||||
return global._performanceEntries.splice(0);
|
||||
return global._performanceEntries.slice(0);
|
||||
}
|
||||
|
||||
function getEntries(type) {
|
||||
function getEntries(type, name) {
|
||||
const result = [];
|
||||
const entries = global._performanceEntries;
|
||||
for (let i = 0; i < entries.length; i += 4) {
|
||||
if (entries[i] === type) {
|
||||
if (entries[i] === type && (name === void 0 || entries[i + 1] === name)) {
|
||||
result.push({
|
||||
type: entries[i],
|
||||
name: entries[i + 1],
|
||||
@@ -53,6 +58,39 @@ define([], function () {
|
||||
});
|
||||
}
|
||||
|
||||
function getEntry(type, name) {
|
||||
const entries = global._performanceEntries;
|
||||
for (let i = 0; i < entries.length; i += 4) {
|
||||
if (entries[i] === type && entries[i + 1] === name) {
|
||||
return {
|
||||
type: entries[i],
|
||||
name: entries[i + 1],
|
||||
startTime: entries[i + 2],
|
||||
duration: entries[i + 3],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getDuration(from, to) {
|
||||
const entries = global._performanceEntries;
|
||||
let name = from;
|
||||
let startTime = 0;
|
||||
for (let i = 0; i < entries.length; i += 4) {
|
||||
if (entries[i + 1] === name) {
|
||||
if (name === from) {
|
||||
// found `from` (start of interval)
|
||||
name = to;
|
||||
startTime = entries[i + 2];
|
||||
} else {
|
||||
// from `to` (end of interval)
|
||||
return entries[i + 2] - startTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function mark(name) {
|
||||
global._performanceEntries.push('mark', name, _now(), 0);
|
||||
if (typeof console.timeStamp === 'function') {
|
||||
@@ -103,6 +141,8 @@ define([], function () {
|
||||
measure: measure,
|
||||
time: time,
|
||||
getEntries: getEntries,
|
||||
getEntry: getEntry,
|
||||
getDuration: getDuration,
|
||||
importEntries: importEntries,
|
||||
exportEntries: exportEntries
|
||||
};
|
||||
|
||||
@@ -4,20 +4,19 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
// --- THIS FILE IS TEMPORARY UNTIL ENV.TS IS CLEANED UP. IT CAN SAFELY BE USED IN ALL TARGET EXECUTION ENVIRONMENTS (node & dom) ---
|
||||
|
||||
let _isWindows = false;
|
||||
let _isMacintosh = false;
|
||||
let _isLinux = false;
|
||||
let _isRootUser = false;
|
||||
let _isNative = false;
|
||||
let _isWeb = false;
|
||||
let _locale: string = undefined;
|
||||
let _language: string = undefined;
|
||||
let _translationsConfigFile: string = undefined;
|
||||
|
||||
interface NLSConfig {
|
||||
locale: string;
|
||||
availableLanguages: { [key: string]: string; };
|
||||
_translationsConfigFile: string;
|
||||
}
|
||||
|
||||
export interface IProcessEnvironment {
|
||||
@@ -28,6 +27,7 @@ interface INodeProcess {
|
||||
platform: string;
|
||||
env: IProcessEnvironment;
|
||||
getuid(): number;
|
||||
nextTick: Function;
|
||||
}
|
||||
declare let process: INodeProcess;
|
||||
declare let global: any;
|
||||
@@ -42,25 +42,25 @@ declare let self: any;
|
||||
export const LANGUAGE_DEFAULT = 'en';
|
||||
|
||||
// OS detection
|
||||
if (typeof process === 'object') {
|
||||
if (typeof process === 'object' && typeof process.nextTick === 'function') {
|
||||
_isWindows = (process.platform === 'win32');
|
||||
_isMacintosh = (process.platform === 'darwin');
|
||||
_isLinux = (process.platform === 'linux');
|
||||
_isRootUser = !_isWindows && (process.getuid() === 0);
|
||||
let rawNlsConfig = process.env['VSCODE_NLS_CONFIG'];
|
||||
const rawNlsConfig = process.env['VSCODE_NLS_CONFIG'];
|
||||
if (rawNlsConfig) {
|
||||
try {
|
||||
let nlsConfig: NLSConfig = JSON.parse(rawNlsConfig);
|
||||
let resolved = nlsConfig.availableLanguages['*'];
|
||||
const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig);
|
||||
const resolved = nlsConfig.availableLanguages['*'];
|
||||
_locale = nlsConfig.locale;
|
||||
// VSCode's default language is 'en'
|
||||
_language = resolved ? resolved : LANGUAGE_DEFAULT;
|
||||
_translationsConfigFile = nlsConfig._translationsConfigFile;
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
_isNative = true;
|
||||
} else if (typeof navigator === 'object') {
|
||||
let userAgent = navigator.userAgent;
|
||||
const userAgent = navigator.userAgent;
|
||||
_isWindows = userAgent.indexOf('Windows') >= 0;
|
||||
_isMacintosh = userAgent.indexOf('Macintosh') >= 0;
|
||||
_isLinux = userAgent.indexOf('Linux') >= 0;
|
||||
@@ -90,11 +90,14 @@ if (_isNative) {
|
||||
export const isWindows = _isWindows;
|
||||
export const isMacintosh = _isMacintosh;
|
||||
export const isLinux = _isLinux;
|
||||
export const isRootUser = _isRootUser;
|
||||
export const isNative = _isNative;
|
||||
export const isWeb = _isWeb;
|
||||
export const platform = _platform;
|
||||
|
||||
export function isRootUser(): boolean {
|
||||
return _isNative && !_isWindows && (process.getuid() === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* The language used for the user interface. The format of
|
||||
* the string is all lower case (e.g. zh-tw for Traditional
|
||||
@@ -109,30 +112,14 @@ export const language = _language;
|
||||
*/
|
||||
export const locale = _locale;
|
||||
|
||||
export interface TimeoutToken {
|
||||
}
|
||||
/**
|
||||
* The translatios that are available through language packs.
|
||||
*/
|
||||
export const translationsConfigFile = _translationsConfigFile;
|
||||
|
||||
export interface IntervalToken {
|
||||
}
|
||||
|
||||
interface IGlobals {
|
||||
Worker?: any;
|
||||
setTimeout(callback: (...args: any[]) => void, delay: number, ...args: any[]): TimeoutToken;
|
||||
clearTimeout(token: TimeoutToken): void;
|
||||
|
||||
setInterval(callback: (...args: any[]) => void, delay: number, ...args: any[]): IntervalToken;
|
||||
clearInterval(token: IntervalToken): void;
|
||||
}
|
||||
|
||||
const _globals = <IGlobals>(typeof self === 'object' ? self : global);
|
||||
const _globals = (typeof self === 'object' ? self : typeof global === 'object' ? global : {} as any);
|
||||
export const globals: any = _globals;
|
||||
|
||||
export const setTimeout = _globals.setTimeout.bind(_globals);
|
||||
export const clearTimeout = _globals.clearTimeout.bind(_globals);
|
||||
|
||||
export const setInterval = _globals.setInterval.bind(_globals);
|
||||
export const clearInterval = _globals.clearInterval.bind(_globals);
|
||||
|
||||
export const enum OperatingSystem {
|
||||
Windows = 1,
|
||||
Macintosh = 2,
|
||||
|
||||
@@ -12,9 +12,9 @@ export function basenameOrAuthority(resource: uri): string {
|
||||
return paths.basename(resource.fsPath) || resource.authority;
|
||||
}
|
||||
|
||||
export function isEqualOrParent(first: uri, second: uri, ignoreCase?: boolean): boolean {
|
||||
if (first.scheme === second.scheme && first.authority === second.authority) {
|
||||
return paths.isEqualOrParent(first.fsPath, second.fsPath, ignoreCase);
|
||||
export function isEqualOrParent(resource: uri, candidate: uri, ignoreCase?: boolean): boolean {
|
||||
if (resource.scheme === candidate.scheme && resource.authority === candidate.authority) {
|
||||
return paths.isEqualOrParent(resource.fsPath, candidate.fsPath, ignoreCase);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -38,7 +38,32 @@ export function isEqual(first: uri, second: uri, ignoreCase?: boolean): boolean
|
||||
}
|
||||
|
||||
export function dirname(resource: uri): uri {
|
||||
const dirname = paths.dirname(resource.path);
|
||||
if (resource.authority && dirname && !paths.isAbsolute(dirname)) {
|
||||
return null; // If a URI contains an authority component, then the path component must either be empty or begin with a slash ("/") character
|
||||
}
|
||||
|
||||
return resource.with({
|
||||
path: paths.dirname(resource.path)
|
||||
path: dirname
|
||||
});
|
||||
}
|
||||
|
||||
export function distinctParents<T>(items: T[], resourceAccessor: (item: T) => uri): T[] {
|
||||
const distinctParents: T[] = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const candidateResource = resourceAccessor(items[i]);
|
||||
if (items.some((otherItem, index) => {
|
||||
if (index === i) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isEqualOrParent(candidateResource, resourceAccessor(otherItem));
|
||||
})) {
|
||||
continue;
|
||||
}
|
||||
|
||||
distinctParents.push(items[i]);
|
||||
}
|
||||
|
||||
return distinctParents;
|
||||
}
|
||||
@@ -712,3 +712,15 @@ export function fuzzyContains(target: string, query: string): boolean {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function containsUppercaseCharacter(target: string, ignoreEscapedChars = false): boolean {
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ignoreEscapedChars) {
|
||||
target = target.replace(/\\./g, '');
|
||||
}
|
||||
|
||||
return target.toLowerCase() !== target;
|
||||
}
|
||||
|
||||
@@ -174,27 +174,27 @@ export default class URI implements UriComponents {
|
||||
if (scheme === void 0) {
|
||||
scheme = this.scheme;
|
||||
} else if (scheme === null) {
|
||||
scheme = '';
|
||||
scheme = _empty;
|
||||
}
|
||||
if (authority === void 0) {
|
||||
authority = this.authority;
|
||||
} else if (authority === null) {
|
||||
authority = '';
|
||||
authority = _empty;
|
||||
}
|
||||
if (path === void 0) {
|
||||
path = this.path;
|
||||
} else if (path === null) {
|
||||
path = '';
|
||||
path = _empty;
|
||||
}
|
||||
if (query === void 0) {
|
||||
query = this.query;
|
||||
} else if (query === null) {
|
||||
query = '';
|
||||
query = _empty;
|
||||
}
|
||||
if (fragment === void 0) {
|
||||
fragment = this.fragment;
|
||||
} else if (fragment === null) {
|
||||
fragment = '';
|
||||
fragment = _empty;
|
||||
}
|
||||
|
||||
if (scheme === this.scheme
|
||||
@@ -315,10 +315,16 @@ export default class URI implements UriComponents {
|
||||
}
|
||||
|
||||
static revive(data: UriComponents | any): URI {
|
||||
let result = new _URI(data);
|
||||
result._fsPath = (<UriState>data).fsPath;
|
||||
result._formatted = (<UriState>data).external;
|
||||
return result;
|
||||
if (!data) {
|
||||
return data;
|
||||
} else if (data instanceof URI) {
|
||||
return data;
|
||||
} else {
|
||||
let result = new _URI(data);
|
||||
result._fsPath = (<UriState>data).fsPath;
|
||||
result._formatted = (<UriState>data).external;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
(function() {
|
||||
|
||||
var _modules = {};
|
||||
var _modules = Object.create(null);//{};
|
||||
_modules["WinJS/Core/_WinJS"] = {};
|
||||
|
||||
var _winjs = function(moduleId, deps, factory) {
|
||||
@@ -64,11 +64,24 @@ _winjs("WinJS/Core/_BaseCoreUtils", ["WinJS/Core/_Global"], function baseCoreUti
|
||||
return func;
|
||||
}
|
||||
|
||||
var actualSetImmediate = null;
|
||||
|
||||
return {
|
||||
hasWinRT: hasWinRT,
|
||||
markSupportedForProcessing: markSupportedForProcessing,
|
||||
_setImmediate: _Global.setImmediate ? _Global.setImmediate.bind(_Global) : function (handler) {
|
||||
_Global.setTimeout(handler, 0);
|
||||
_setImmediate: function (callback) {
|
||||
// BEGIN monaco change
|
||||
if (actualSetImmediate === null) {
|
||||
if (_Global.setImmediate) {
|
||||
actualSetImmediate = _Global.setImmediate.bind(_Global);
|
||||
} else if (typeof process !== 'undefined' && typeof process.nextTick === 'function') {
|
||||
actualSetImmediate = process.nextTick.bind(process);
|
||||
} else {
|
||||
actualSetImmediate = _Global.setTimeout.bind(_Global);
|
||||
}
|
||||
}
|
||||
actualSetImmediate(callback);
|
||||
// END monaco change
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -2057,15 +2070,9 @@ _winjs("WinJS/Promise", ["WinJS/Core/_Base","WinJS/Promise/_StateMachine"], func
|
||||
var exported = _modules["WinJS/Core/_WinJS"];
|
||||
|
||||
if (typeof exports === 'undefined' && typeof define === 'function' && define.amd) {
|
||||
define(exported);
|
||||
define([], exported);
|
||||
} else {
|
||||
module.exports = exported;
|
||||
}
|
||||
|
||||
if (typeof process !== 'undefined' && typeof process.nextTick === 'function') {
|
||||
_modules["WinJS/Core/_BaseCoreUtils"]._setImmediate = function(handler) {
|
||||
return process.nextTick(handler);
|
||||
};
|
||||
}
|
||||
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -163,6 +163,10 @@ class SimpleWorkerProtocol {
|
||||
err: undefined
|
||||
});
|
||||
}, (e) => {
|
||||
if (e.detail instanceof Error) {
|
||||
// Loading errors have a detail property that points to the actual error
|
||||
e.detail = transformErrorForSerialization(e.detail);
|
||||
}
|
||||
this._send({
|
||||
vsWorker: this._workerId,
|
||||
seq: req,
|
||||
@@ -339,13 +343,6 @@ export class SimpleWorkerServer {
|
||||
delete loaderConfig.paths['vs'];
|
||||
}
|
||||
}
|
||||
let nlsConfig = loaderConfig['vs/nls'];
|
||||
// We need to have pseudo translation
|
||||
if (nlsConfig && nlsConfig.pseudo) {
|
||||
require(['vs/nls'], function (nlsPlugin) {
|
||||
nlsPlugin.setPseudoTranslation(nlsConfig.pseudo);
|
||||
});
|
||||
}
|
||||
|
||||
// Since this is in a web worker, enable catching errors
|
||||
loaderConfig.catchError = true;
|
||||
|
||||
@@ -151,20 +151,16 @@ export class ConfigWatcher<T> implements IConfigWatcher<T>, IDisposable {
|
||||
return; // avoid watchers that will never get disposed by checking for being disposed
|
||||
}
|
||||
|
||||
try {
|
||||
const watcher = extfs.watch(path, (type, file) => this.onConfigFileChange(type, file, isParentFolder));
|
||||
watcher.on('error', (code: number, signal: string) => this.options.onError(`Error watching ${path} for configuration changes (${code}, ${signal})`));
|
||||
const watcher = extfs.watch(path,
|
||||
(type, file) => this.onConfigFileChange(type, file, isParentFolder),
|
||||
(error: string) => this.options.onError(error)
|
||||
);
|
||||
|
||||
if (watcher) {
|
||||
this.disposables.push(toDisposable(() => {
|
||||
watcher.removeAllListeners();
|
||||
watcher.close();
|
||||
}));
|
||||
} catch (error) {
|
||||
fs.exists(path, exists => {
|
||||
if (exists) {
|
||||
this.options.onError(`Failed to watch ${path} for configuration changes (${error.toString()})`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ export function getFirstFrame(arg0: IRemoteConsoleLog | string): IStackFrame {
|
||||
// at e.$executeContributedCommand(c:\Users\someone\Desktop\end-js\extension.js:19:17)
|
||||
const stack = arg0;
|
||||
if (stack) {
|
||||
const topFrame = stack.split('\n')[0];
|
||||
const topFrame = findFirstFrame(stack);
|
||||
|
||||
// at [^\/]* => line starts with "at" followed by any character except '/' (to not capture unix paths too late)
|
||||
// (?:(?:[a-zA-Z]+:)|(?:[\/])|(?:\\\\) => windows drive letter OR unix root OR unc root
|
||||
@@ -88,12 +88,25 @@ export function getFirstFrame(arg0: IRemoteConsoleLog | string): IStackFrame {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
function findFirstFrame(stack: string): string {
|
||||
if (!stack) {
|
||||
return stack;
|
||||
}
|
||||
|
||||
const newlineIndex = stack.indexOf('\n');
|
||||
if (newlineIndex === -1) {
|
||||
return stack;
|
||||
}
|
||||
|
||||
return stack.substring(0, newlineIndex);
|
||||
}
|
||||
|
||||
export function log(entry: IRemoteConsoleLog, label: string): void {
|
||||
const { args, stack } = parse(entry);
|
||||
|
||||
const isOneStringArg = typeof args[0] === 'string' && args.length === 1;
|
||||
|
||||
let topFrame = stack && stack.split('\n')[0];
|
||||
let topFrame = findFirstFrame(stack);
|
||||
if (topFrame) {
|
||||
topFrame = `(${topFrame.trim()})`;
|
||||
}
|
||||
|
||||
@@ -28,11 +28,11 @@ export function bomLength(encoding: string): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function decode(buffer: NodeBuffer, encoding: string, options?: any): string {
|
||||
return iconv.decode(buffer, toNodeEncoding(encoding), options);
|
||||
export function decode(buffer: NodeBuffer, encoding: string): string {
|
||||
return iconv.decode(buffer, toNodeEncoding(encoding));
|
||||
}
|
||||
|
||||
export function encode(content: string, encoding: string, options?: any): NodeBuffer {
|
||||
export function encode(content: string | NodeBuffer, encoding: string, options?: { addBOM?: boolean }): NodeBuffer {
|
||||
return iconv.encode(content, toNodeEncoding(encoding), options);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,10 @@ export function decodeStream(encoding: string): NodeJS.ReadWriteStream {
|
||||
return iconv.decodeStream(toNodeEncoding(encoding));
|
||||
}
|
||||
|
||||
export function encodeStream(encoding: string, options?: { addBOM?: boolean }): NodeJS.ReadWriteStream {
|
||||
return iconv.encodeStream(toNodeEncoding(encoding), options);
|
||||
}
|
||||
|
||||
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
|
||||
@@ -181,6 +185,7 @@ const windowsTerminalEncodings = {
|
||||
'865': 'cp865', // Nordic
|
||||
'866': 'cp866', // Russian
|
||||
'869': 'cp869', // Modern Greek
|
||||
'936': 'cp936', // Simplified Chinese
|
||||
'1252': 'cp1252' // West European Latin
|
||||
};
|
||||
|
||||
|
||||
+221
-63
@@ -9,11 +9,11 @@ import * as uuid from 'vs/base/common/uuid';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
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';
|
||||
import { encode, encodeStream } from 'vs/base/node/encoding';
|
||||
|
||||
const loop = flow.loop;
|
||||
|
||||
@@ -43,6 +43,27 @@ export function readdir(path: string, callback: (error: Error, files: string[])
|
||||
return fs.readdir(path, callback);
|
||||
}
|
||||
|
||||
export interface IStatAndLink {
|
||||
stat: fs.Stats;
|
||||
isSymbolicLink: boolean;
|
||||
}
|
||||
|
||||
export function statLink(path: string, callback: (error: Error, statAndIsLink: IStatAndLink) => void): void {
|
||||
fs.lstat(path, (error, lstat) => {
|
||||
if (error || lstat.isSymbolicLink()) {
|
||||
fs.stat(path, (error, stat) => {
|
||||
if (error) {
|
||||
return callback(error, null);
|
||||
}
|
||||
|
||||
callback(null, { stat, isSymbolicLink: lstat && lstat.isSymbolicLink() });
|
||||
});
|
||||
} else {
|
||||
callback(null, { stat: lstat, isSymbolicLink: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function copy(source: string, target: string, callback: (error: Error) => void, copiedSources?: { [path: string]: boolean }): void {
|
||||
if (!copiedSources) {
|
||||
copiedSources = Object.create(null);
|
||||
@@ -54,7 +75,7 @@ export function copy(source: string, target: string, callback: (error: Error) =>
|
||||
}
|
||||
|
||||
if (!stat.isDirectory()) {
|
||||
return pipeFs(source, target, stat.mode & 511, callback);
|
||||
return doCopyFile(source, target, stat.mode & 511, callback);
|
||||
}
|
||||
|
||||
if (copiedSources[source]) {
|
||||
@@ -75,6 +96,38 @@ export function copy(source: string, target: string, callback: (error: Error) =>
|
||||
});
|
||||
}
|
||||
|
||||
function doCopyFile(source: string, target: string, mode: number, callback: (error: Error) => void): void {
|
||||
const reader = fs.createReadStream(source);
|
||||
const writer = fs.createWriteStream(target, { mode });
|
||||
|
||||
let finished = false;
|
||||
const finish = (error?: Error) => {
|
||||
if (!finished) {
|
||||
finished = true;
|
||||
|
||||
// in error cases, pass to callback
|
||||
if (error) {
|
||||
callback(error);
|
||||
}
|
||||
|
||||
// we need to explicitly chmod because of https://github.com/nodejs/node/issues/1104
|
||||
else {
|
||||
fs.chmod(target, mode, callback);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// handle errors properly
|
||||
reader.once('error', error => finish(error));
|
||||
writer.once('error', error => finish(error));
|
||||
|
||||
// we are done (underlying fd has been closed)
|
||||
writer.once('close', () => finish());
|
||||
|
||||
// start piping
|
||||
reader.pipe(writer);
|
||||
}
|
||||
|
||||
export function mkdirp(path: string, mode?: number): TPromise<boolean> {
|
||||
const mkdir = () => nfcall(fs.mkdir, path, mode)
|
||||
.then(null, (err: NodeJS.ErrnoException) => {
|
||||
@@ -88,11 +141,12 @@ export function mkdirp(path: string, mode?: number): TPromise<boolean> {
|
||||
return TPromise.wrapError<boolean>(err);
|
||||
});
|
||||
|
||||
// is root?
|
||||
// stop at root
|
||||
if (path === paths.dirname(path)) {
|
||||
return TPromise.as(true);
|
||||
}
|
||||
|
||||
// recursively mkdir
|
||||
return mkdir().then(null, (err: NodeJS.ErrnoException) => {
|
||||
if (err.code === 'ENOENT') {
|
||||
return mkdirp(paths.dirname(path), mode).then(mkdir);
|
||||
@@ -102,40 +156,6 @@ export function mkdirp(path: string, mode?: number): TPromise<boolean> {
|
||||
});
|
||||
}
|
||||
|
||||
function pipeFs(source: string, target: string, mode: number, callback: (error: Error) => void): void {
|
||||
let callbackHandled = false;
|
||||
|
||||
const readStream = fs.createReadStream(source);
|
||||
const writeStream = fs.createWriteStream(target, { mode: mode });
|
||||
|
||||
const onError = (error: Error) => {
|
||||
if (!callbackHandled) {
|
||||
callbackHandled = true;
|
||||
callback(error);
|
||||
}
|
||||
};
|
||||
|
||||
readStream.on('error', onError);
|
||||
writeStream.on('error', onError);
|
||||
|
||||
readStream.on('end', () => {
|
||||
(<any>writeStream).end(() => { // In this case the write stream is known to have an end signature with callback
|
||||
if (!callbackHandled) {
|
||||
callbackHandled = true;
|
||||
|
||||
fs.chmod(target, mode, callback); // we need to explicitly chmod because of https://github.com/nodejs/node/issues/1104
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// In node 0.8 there is no easy way to find out when the pipe operation has finished. As such, we use the end property = false
|
||||
// so that we are in charge of calling end() on the write stream and we will be notified when the write stream is really done.
|
||||
// We can do this because file streams have an end() method that allows to pass in a callback.
|
||||
// In node 0.10 there is an event 'finish' emitted from the write stream that can be used. See
|
||||
// https://groups.google.com/forum/?fromgroups=#!topic/nodejs/YWQ1sRoXOdI
|
||||
readStream.pipe(writeStream, { end: false });
|
||||
}
|
||||
|
||||
// Deletes the given path by first moving it out of the workspace. This has two benefits. For one, the operation can return fast because
|
||||
// after the rename, the contents are out of the workspace although not yet deleted. The greater benefit however is that this operation
|
||||
// will fail in case any file is used by another process. fs.unlink() in node will not bail if a file unlinked is used by another process.
|
||||
@@ -320,19 +340,124 @@ export function mv(source: string, target: string, callback: (error: Error) => v
|
||||
});
|
||||
}
|
||||
|
||||
export interface IWriteFileOptions {
|
||||
mode?: number;
|
||||
flag?: string;
|
||||
encoding?: {
|
||||
charset: string;
|
||||
addBOM: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
let canFlush = true;
|
||||
export function writeFileAndFlush(path: string, data: string | NodeBuffer | NodeJS.ReadableStream, options: IWriteFileOptions, callback: (error?: Error) => void): void {
|
||||
options = ensureOptions(options);
|
||||
|
||||
if (typeof data === 'string' || Buffer.isBuffer(data)) {
|
||||
doWriteFileAndFlush(path, data, options, callback);
|
||||
} else {
|
||||
doWriteFileStreamAndFlush(path, data, options, callback);
|
||||
}
|
||||
}
|
||||
|
||||
function doWriteFileStreamAndFlush(path: string, reader: NodeJS.ReadableStream, options: IWriteFileOptions, callback: (error?: Error) => void): void {
|
||||
|
||||
// finish only once
|
||||
let finished = false;
|
||||
const finish = (error?: Error) => {
|
||||
if (!finished) {
|
||||
finished = true;
|
||||
|
||||
// in error cases we need to manually close streams
|
||||
// if the write stream was successfully opened
|
||||
if (error) {
|
||||
if (isOpen) {
|
||||
writer.once('close', () => callback(error));
|
||||
writer.close();
|
||||
} else {
|
||||
callback(error);
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise just return without error
|
||||
else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// create writer to target. we set autoClose: false because we want to use the streams
|
||||
// file descriptor to call fs.fdatasync to ensure the data is flushed to disk
|
||||
const writer = fs.createWriteStream(path, { mode: options.mode, flags: options.flag, autoClose: false });
|
||||
|
||||
// Event: 'open'
|
||||
// Purpose: save the fd for later use and start piping
|
||||
// Notes: will not be called when there is an error opening the file descriptor!
|
||||
let fd: number;
|
||||
let isOpen: boolean;
|
||||
writer.once('open', descriptor => {
|
||||
fd = descriptor;
|
||||
isOpen = true;
|
||||
|
||||
// if an encoding is provided, we need to pipe the stream through
|
||||
// an encoder stream and forward the encoding related options
|
||||
if (options.encoding) {
|
||||
reader = reader.pipe(encodeStream(options.encoding.charset, { addBOM: options.encoding.addBOM }));
|
||||
}
|
||||
|
||||
// start data piping only when we got a successful open. this ensures that we do
|
||||
// not consume the stream when an error happens and helps to fix this issue:
|
||||
// https://github.com/Microsoft/vscode/issues/42542
|
||||
reader.pipe(writer);
|
||||
});
|
||||
|
||||
// Event: 'error'
|
||||
// Purpose: to return the error to the outside and to close the write stream (does not happen automatically)
|
||||
reader.once('error', error => finish(error));
|
||||
writer.once('error', error => finish(error));
|
||||
|
||||
// Event: 'finish'
|
||||
// Purpose: use fs.fdatasync to flush the contents to disk
|
||||
// Notes: event is called when the writer has finished writing to the underlying resource. we must call writer.close()
|
||||
// because we have created the WriteStream with autoClose: false
|
||||
writer.once('finish', () => {
|
||||
|
||||
// flush to disk
|
||||
if (canFlush && isOpen) {
|
||||
fs.fdatasync(fd, (syncError: Error) => {
|
||||
|
||||
// In some exotic setups it is well possible that node fails to sync
|
||||
// In that case we disable flushing and warn to the console
|
||||
if (syncError) {
|
||||
console.warn('[node.js fs] fdatasync is now disabled for this session because it failed: ', syncError);
|
||||
canFlush = false;
|
||||
}
|
||||
|
||||
writer.close();
|
||||
});
|
||||
} else {
|
||||
writer.close();
|
||||
}
|
||||
});
|
||||
|
||||
// Event: 'close'
|
||||
// Purpose: signal we are done to the outside
|
||||
// Notes: event is called when the writer's filedescriptor is closed
|
||||
writer.once('close', () => finish());
|
||||
}
|
||||
|
||||
// Calls fs.writeFile() followed by a fs.sync() call to flush the changes to disk
|
||||
// We do this in cases where we want to make sure the data is really on disk and
|
||||
// not in some cache.
|
||||
//
|
||||
// See https://github.com/nodejs/node/blob/v5.10.0/lib/fs.js#L1194
|
||||
let canFlush = true;
|
||||
export function writeFileAndFlush(path: string, data: string | NodeBuffer, options: { mode?: number; flag?: string; }, callback: (error: Error) => void): void {
|
||||
if (!canFlush) {
|
||||
return fs.writeFile(path, data, options, callback);
|
||||
function doWriteFileAndFlush(path: string, data: string | NodeBuffer, options: IWriteFileOptions, callback: (error?: Error) => void): void {
|
||||
if (options.encoding) {
|
||||
data = encode(data, options.encoding.charset, { addBOM: options.encoding.addBOM });
|
||||
}
|
||||
|
||||
if (!options) {
|
||||
options = { mode: 0o666, flag: 'w' };
|
||||
if (!canFlush) {
|
||||
return fs.writeFile(path, data, { mode: options.mode, flag: options.flag }, callback);
|
||||
}
|
||||
|
||||
// Open the file with same flags and mode as fs.writeFile()
|
||||
@@ -363,13 +488,15 @@ 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);
|
||||
export function writeFileAndFlushSync(path: string, data: string | NodeBuffer, options?: IWriteFileOptions): void {
|
||||
options = ensureOptions(options);
|
||||
|
||||
if (options.encoding) {
|
||||
data = encode(data, options.encoding.charset, { addBOM: options.encoding.addBOM });
|
||||
}
|
||||
|
||||
if (!options) {
|
||||
options = { mode: 0o666, flag: 'w' };
|
||||
if (!canFlush) {
|
||||
return fs.writeFileSync(path, data, { mode: options.mode, flag: options.flag });
|
||||
}
|
||||
|
||||
// Open the file with same flags and mode as fs.writeFile()
|
||||
@@ -392,6 +519,24 @@ export function writeFileAndFlushSync(path: string, data: string | NodeBuffer, o
|
||||
}
|
||||
}
|
||||
|
||||
function ensureOptions(options?: IWriteFileOptions): IWriteFileOptions {
|
||||
if (!options) {
|
||||
return { mode: 0o666, flag: 'w' };
|
||||
}
|
||||
|
||||
const ensuredOptions: IWriteFileOptions = { mode: options.mode, flag: options.flag, encoding: options.encoding };
|
||||
|
||||
if (typeof ensuredOptions.mode !== 'number') {
|
||||
ensuredOptions.mode = 0o666;
|
||||
}
|
||||
|
||||
if (typeof ensuredOptions.flag !== 'string') {
|
||||
ensuredOptions.flag = 'w';
|
||||
}
|
||||
|
||||
return ensuredOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copied from: https://github.com/Microsoft/vscode-node-debug/blob/master/src/node/pathUtilities.ts#L83
|
||||
*
|
||||
@@ -474,21 +619,34 @@ function normalizePath(path: string): string {
|
||||
return strings.rtrim(paths.normalize(path), paths.sep);
|
||||
}
|
||||
|
||||
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: 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);
|
||||
export function watch(path: string, onChange: (type: string, path: string) => void, onError: (error: string) => void): fs.FSWatcher {
|
||||
try {
|
||||
const watcher = fs.watch(path);
|
||||
|
||||
watcher.on('change', (type, raw) => {
|
||||
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);
|
||||
});
|
||||
onChange(type, file);
|
||||
});
|
||||
|
||||
return watcher;
|
||||
}
|
||||
watcher.on('error', (code: number, signal: string) => onError(`Failed to watch ${path} for changes (${code}, ${signal})`));
|
||||
|
||||
return watcher;
|
||||
} catch (error) {
|
||||
fs.exists(path, exists => {
|
||||
if (exists) {
|
||||
onError(`Failed to watch ${path} for changes (${error.toString()})`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
+19
-8
@@ -19,7 +19,7 @@ export function readdir(path: string): TPromise<string[]> {
|
||||
}
|
||||
|
||||
export function exists(path: string): TPromise<boolean> {
|
||||
return new TPromise(c => fs.exists(path, c));
|
||||
return new TPromise(c => fs.exists(path, c), () => { });
|
||||
}
|
||||
|
||||
export function chmod(path: string, mode: number): TPromise<boolean> {
|
||||
@@ -54,6 +54,10 @@ export function stat(path: string): TPromise<fs.Stats> {
|
||||
return nfcall(fs.stat, path);
|
||||
}
|
||||
|
||||
export function statLink(path: string): TPromise<{ stat: fs.Stats, isSymbolicLink: boolean }> {
|
||||
return nfcall(extfs.statLink, path);
|
||||
}
|
||||
|
||||
export function lstat(path: string): TPromise<fs.Stats> {
|
||||
return nfcall(fs.lstat, path);
|
||||
}
|
||||
@@ -99,10 +103,11 @@ export function readFile(path: string, encoding?: string): TPromise<Buffer | str
|
||||
// Therefor we use a Queue on the path that is given to us to sequentialize calls to the same path properly.
|
||||
const writeFilePathQueue: { [path: string]: Queue<void> } = Object.create(null);
|
||||
|
||||
export function writeFile(path: string, data: string, options?: { mode?: number; flag?: string; }): TPromise<void>;
|
||||
export function writeFile(path: string, data: NodeBuffer, options?: { mode?: number; flag?: string; }): TPromise<void>;
|
||||
export function writeFile(path: string, data: any, options?: { mode?: number; flag?: string; }): TPromise<void> {
|
||||
let queueKey = toQueueKey(path);
|
||||
export function writeFile(path: string, data: string, options?: extfs.IWriteFileOptions): TPromise<void>;
|
||||
export function writeFile(path: string, data: NodeBuffer, options?: extfs.IWriteFileOptions): TPromise<void>;
|
||||
export function writeFile(path: string, data: NodeJS.ReadableStream, options?: extfs.IWriteFileOptions): TPromise<void>;
|
||||
export function writeFile(path: string, data: any, options?: extfs.IWriteFileOptions): TPromise<void> {
|
||||
const queueKey = toQueueKey(path);
|
||||
|
||||
return ensureWriteFileQueue(queueKey).queue(() => nfcall(extfs.writeFileAndFlush, path, data, options));
|
||||
}
|
||||
@@ -160,8 +165,14 @@ export function fileExists(path: string): TPromise<boolean> {
|
||||
/**
|
||||
* Deletes a path from disk.
|
||||
*/
|
||||
const tmpDir = os.tmpdir();
|
||||
export function del(path: string, tmp = tmpDir): TPromise<void> {
|
||||
let _tmpDir: string = null;
|
||||
function getTmpDir(): string {
|
||||
if (!_tmpDir) {
|
||||
_tmpDir = os.tmpdir();
|
||||
}
|
||||
return _tmpDir;
|
||||
}
|
||||
export function del(path: string, tmp = getTmpDir()): TPromise<void> {
|
||||
return nfcall(extfs.del, path, tmp);
|
||||
}
|
||||
|
||||
@@ -184,4 +195,4 @@ export function whenDeleted(path: string): TPromise<void> {
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,15 @@
|
||||
|
||||
import net = require('net');
|
||||
|
||||
/**
|
||||
* @returns Returns a random port between 1025 and 65535.
|
||||
*/
|
||||
export function randomPort(): number {
|
||||
let min = 1025;
|
||||
let max = 65535;
|
||||
return min + Math.floor((max - min) * Math.random());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
||||
@@ -6,9 +6,6 @@
|
||||
|
||||
import path = require('path');
|
||||
import * as cp from 'child_process';
|
||||
import ChildProcess = cp.ChildProcess;
|
||||
import exec = cp.exec;
|
||||
import spawn = cp.spawn;
|
||||
import { fork } from 'vs/base/node/stdFork';
|
||||
import nls = require('vs/nls');
|
||||
import { PPromise, TPromise, TValueCallback, TProgressCallback, ErrorCallback } from 'vs/base/common/winjs.base';
|
||||
@@ -40,7 +37,7 @@ function getWindowsCode(status: number): TerminateResponseCode {
|
||||
}
|
||||
}
|
||||
|
||||
export function terminateProcess(process: ChildProcess, cwd?: string): TerminateResponse {
|
||||
export function terminateProcess(process: cp.ChildProcess, cwd?: string): TerminateResponse {
|
||||
if (Platform.isWindows) {
|
||||
try {
|
||||
let options: any = {
|
||||
@@ -80,8 +77,8 @@ export abstract class AbstractProcess<TProgressData> {
|
||||
private options: CommandOptions | ForkOptions;
|
||||
protected shell: boolean;
|
||||
|
||||
private childProcess: ChildProcess;
|
||||
protected childProcessPromise: TPromise<ChildProcess>;
|
||||
private childProcess: cp.ChildProcess;
|
||||
protected childProcessPromise: TPromise<cp.ChildProcess>;
|
||||
protected terminateRequested: boolean;
|
||||
|
||||
private static WellKnowCommands: IStringDictionary<boolean> = {
|
||||
@@ -173,7 +170,7 @@ export abstract class AbstractProcess<TProgressData> {
|
||||
if (this.args) {
|
||||
cmd = cmd + ' ' + this.args.join(' ');
|
||||
}
|
||||
this.childProcess = exec(cmd, this.options, (error, stdout, stderr) => {
|
||||
this.childProcess = cp.exec(cmd, this.options, (error, stdout, stderr) => {
|
||||
this.childProcess = null;
|
||||
let err: any = error;
|
||||
// This is tricky since executing a command shell reports error back in case the executed command return an
|
||||
@@ -186,7 +183,7 @@ export abstract class AbstractProcess<TProgressData> {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let childProcess: ChildProcess = null;
|
||||
let childProcess: cp.ChildProcess = null;
|
||||
let closeHandler = (data: any) => {
|
||||
this.childProcess = null;
|
||||
this.childProcessPromise = null;
|
||||
@@ -231,13 +228,13 @@ export abstract class AbstractProcess<TProgressData> {
|
||||
} else {
|
||||
args.push(commandLine.join(' '));
|
||||
}
|
||||
childProcess = spawn(getWindowsShell(), args, options);
|
||||
childProcess = cp.spawn(getWindowsShell(), args, options);
|
||||
} else {
|
||||
if (this.cmd) {
|
||||
childProcess = spawn(this.cmd, this.args, this.options);
|
||||
childProcess = cp.spawn(this.cmd, this.args, this.options);
|
||||
} else if (this.module) {
|
||||
this.childProcessPromise = new TPromise<ChildProcess>((c, e, p) => {
|
||||
fork(this.module, this.args, <ForkOptions>this.options, (error: any, childProcess: ChildProcess) => {
|
||||
this.childProcessPromise = new TPromise<cp.ChildProcess>((c, e, p) => {
|
||||
fork(this.module, this.args, <ForkOptions>this.options, (error: any, childProcess: cp.ChildProcess) => {
|
||||
if (error) {
|
||||
e(error);
|
||||
ee({ terminated: this.terminateRequested, error: error });
|
||||
@@ -269,7 +266,7 @@ export abstract class AbstractProcess<TProgressData> {
|
||||
}
|
||||
|
||||
protected abstract handleExec(cc: TValueCallback<SuccessData>, pp: TProgressCallback<TProgressData>, error: Error, stdout: Buffer, stderr: Buffer): void;
|
||||
protected abstract handleSpawn(childProcess: ChildProcess, cc: TValueCallback<SuccessData>, pp: TProgressCallback<TProgressData>, ee: ErrorCallback, sync: boolean): void;
|
||||
protected abstract handleSpawn(childProcess: cp.ChildProcess, cc: TValueCallback<SuccessData>, pp: TProgressCallback<TProgressData>, ee: ErrorCallback, sync: boolean): void;
|
||||
|
||||
protected handleClose(data: any, cc: TValueCallback<SuccessData>, pp: TProgressCallback<TProgressData>, ee: ErrorCallback): void {
|
||||
// Default is to do nothing.
|
||||
@@ -315,7 +312,7 @@ export abstract class AbstractProcess<TProgressData> {
|
||||
if (!this.shell || !Platform.isWindows) {
|
||||
c(false);
|
||||
}
|
||||
let cmdShell = spawn(getWindowsShell(), ['/s', '/c']);
|
||||
let cmdShell = cp.spawn(getWindowsShell(), ['/s', '/c']);
|
||||
cmdShell.on('error', (error: Error) => {
|
||||
c(true);
|
||||
});
|
||||
@@ -353,7 +350,7 @@ export class LineProcess extends AbstractProcess<LineData> {
|
||||
cc({ terminated: this.terminateRequested, error: error });
|
||||
}
|
||||
|
||||
protected handleSpawn(childProcess: ChildProcess, cc: TValueCallback<SuccessData>, pp: TProgressCallback<LineData>, ee: ErrorCallback, sync: boolean): void {
|
||||
protected handleSpawn(childProcess: cp.ChildProcess, cc: TValueCallback<SuccessData>, pp: TProgressCallback<LineData>, ee: ErrorCallback, sync: boolean): void {
|
||||
this.stdoutLineDecoder = new LineDecoder();
|
||||
this.stderrLineDecoder = new LineDecoder();
|
||||
childProcess.stdout.on('data', (data: Buffer) => {
|
||||
@@ -384,7 +381,7 @@ export interface IQueuedSender {
|
||||
// queue is free again to consume messages.
|
||||
// On Windows we always wait for the send() method to return before sending the next message
|
||||
// to workaround https://github.com/nodejs/node/issues/7657 (IPC can freeze process)
|
||||
export function createQueuedSender(childProcess: ChildProcess | NodeJS.Process): IQueuedSender {
|
||||
export function createQueuedSender(childProcess: cp.ChildProcess | NodeJS.Process): IQueuedSender {
|
||||
let msgQueue: string[] = [];
|
||||
let useQueue = false;
|
||||
|
||||
|
||||
+27
-4
@@ -7,6 +7,7 @@
|
||||
|
||||
import { spawn, exec } from 'child_process';
|
||||
import * as path from 'path';
|
||||
import * as nls from 'vs/nls';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
export interface ProcessItem {
|
||||
@@ -61,12 +62,30 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
|
||||
function findName(cmd: string): string {
|
||||
|
||||
const RENDERER_PROCESS_HINT = /--disable-blink-features=Auxclick/;
|
||||
const WINDOWS_WATCHER_HINT = /\\watcher\\win32\\CodeHelper.exe/;
|
||||
const WINDOWS_WATCHER_HINT = /\\watcher\\win32\\CodeHelper\.exe/;
|
||||
const WINDOWS_CRASH_REPORTER = /--crashes-directory/;
|
||||
const WINDOWS_PTY = /\\pipe\\winpty-control/;
|
||||
const WINDOWS_CONSOLE_HOST = /conhost\.exe/;
|
||||
const TYPE = /--type=([a-zA-Z-]+)/;
|
||||
|
||||
// find windows file watcher
|
||||
if (WINDOWS_WATCHER_HINT.exec(cmd)) {
|
||||
return 'watcherService';
|
||||
return 'watcherService ';
|
||||
}
|
||||
|
||||
// find windows crash reporter
|
||||
if (WINDOWS_CRASH_REPORTER.exec(cmd)) {
|
||||
return 'electron-crash-reporter';
|
||||
}
|
||||
|
||||
// find windows pty process
|
||||
if (WINDOWS_PTY.exec(cmd)) {
|
||||
return 'winpty-process';
|
||||
}
|
||||
|
||||
//find windows console host process
|
||||
if (WINDOWS_CONSOLE_HOST.exec(cmd)) {
|
||||
return 'console-window-host (Windows internal process)';
|
||||
}
|
||||
|
||||
// find "--type=xxxx"
|
||||
@@ -102,6 +121,8 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
|
||||
console.log(nls.localize('collecting', 'Collecting CPU and memory information. This might take a couple of seconds.'));
|
||||
|
||||
interface ProcessInfo {
|
||||
type: 'processInfo';
|
||||
name: string;
|
||||
@@ -157,7 +178,8 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
|
||||
|
||||
cmd.on('exit', () => {
|
||||
if (stderr.length > 0) {
|
||||
reject(stderr);
|
||||
reject(new Error(stderr));
|
||||
return;
|
||||
}
|
||||
let processItems: Map<number, ProcessItem> = new Map();
|
||||
try {
|
||||
@@ -205,12 +227,13 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
|
||||
reject(new Error(`Root process ${rootPid} not found`));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(stdout);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
} else { // OS X & Linux
|
||||
|
||||
const CMD = 'ps -ax -o pid=,ppid=,pcpu=,pmem=,command=';
|
||||
const CMD = '/bin/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) => {
|
||||
|
||||
@@ -28,7 +28,7 @@ export interface IRequestOptions {
|
||||
password?: string;
|
||||
headers?: any;
|
||||
timeout?: number;
|
||||
data?: any;
|
||||
data?: string | Stream;
|
||||
agent?: Agent;
|
||||
followRedirects?: number;
|
||||
strictSSL?: boolean;
|
||||
@@ -63,6 +63,7 @@ export function request(options: IRequestOptions): TPromise<IRequestContext> {
|
||||
: getNodeRequest(options);
|
||||
|
||||
return rawRequestPromise.then(rawRequest => {
|
||||
|
||||
return new TPromise<IRequestContext>((c, e) => {
|
||||
const endpoint = parseUrl(options.url);
|
||||
|
||||
@@ -83,7 +84,6 @@ export function request(options: IRequestOptions): TPromise<IRequestContext> {
|
||||
|
||||
req = rawRequest(opts, (res: http.ClientResponse) => {
|
||||
const followRedirects = isNumber(options.followRedirects) ? options.followRedirects : 3;
|
||||
|
||||
if (res.statusCode >= 300 && res.statusCode < 400 && followRedirects > 0 && res.headers['location']) {
|
||||
request(assign({}, options, {
|
||||
url: res.headers['location'],
|
||||
@@ -107,7 +107,12 @@ export function request(options: IRequestOptions): TPromise<IRequestContext> {
|
||||
}
|
||||
|
||||
if (options.data) {
|
||||
req.write(options.data);
|
||||
if (typeof options.data === 'string') {
|
||||
req.write(options.data);
|
||||
} else {
|
||||
options.data.pipe(req);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
req.end();
|
||||
@@ -167,7 +172,13 @@ export function asJson<T>(context: IRequestContext): TPromise<T> {
|
||||
|
||||
const buffer: string[] = [];
|
||||
context.stream.on('data', (d: string) => buffer.push(d));
|
||||
context.stream.on('end', () => c(JSON.parse(buffer.join(''))));
|
||||
context.stream.on('end', () => {
|
||||
try {
|
||||
c(JSON.parse(buffer.join('')));
|
||||
} catch (err) {
|
||||
e(err);
|
||||
}
|
||||
});
|
||||
context.stream.on('error', e);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -79,12 +79,12 @@ export function collectWorkspaceStats(folder: string, filter: string[]): Workspa
|
||||
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) {
|
||||
if (token.maxReached) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (statSync(join(dir, file)).isDirectory()) {
|
||||
if (filter.indexOf(file) === -1) {
|
||||
@@ -92,10 +92,11 @@ export function collectWorkspaceStats(folder: string, filter: string[]): Workspa
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (token.count++ >= MAX_FILES) {
|
||||
if (token.count >= MAX_FILES) {
|
||||
token.maxReached = true;
|
||||
return;
|
||||
}
|
||||
token.count++;
|
||||
acceptFile(file);
|
||||
}
|
||||
} catch {
|
||||
|
||||
@@ -50,7 +50,6 @@ function generatePatchedEnv(env: any, stdInPipeName: string, stdOutPipeName: str
|
||||
newEnv['STDOUT_PIPE_NAME'] = stdOutPipeName;
|
||||
newEnv['STDERR_PIPE_NAME'] = stdErrPipeName;
|
||||
newEnv['ELECTRON_RUN_AS_NODE'] = '1';
|
||||
newEnv['ELECTRON_NO_ASAR'] = '1';
|
||||
|
||||
return newEnv;
|
||||
}
|
||||
@@ -138,4 +137,4 @@ export function fork(modulePath: string, args: string[], options: IForkOpts, cal
|
||||
|
||||
// On vscode exit still close server #7758
|
||||
process.once('exit', closeServer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ log('ELECTRON_RUN_AS_NODE: ' + process.env['ELECTRON_RUN_AS_NODE']);
|
||||
|
||||
var fsWriteSyncString = function (fd, str, position, encoding) {
|
||||
// fs.writeSync(fd, string[, position[, encoding]]);
|
||||
var buf = new Buffer(str, encoding || 'utf8');
|
||||
var buf = Buffer.from(str, encoding || 'utf8');
|
||||
return fsWriteSyncBuffer(fd, buf, 0, buf.length);
|
||||
};
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ export function readExactlyByFile(file: string, totalBytes: number): TPromise<Re
|
||||
});
|
||||
}
|
||||
|
||||
const buffer = new Buffer(totalBytes);
|
||||
const buffer = Buffer.allocUnsafe(totalBytes);
|
||||
let offset = 0;
|
||||
|
||||
function readChunk(): void {
|
||||
@@ -96,7 +96,7 @@ export function readToMatchingString(file: string, matchingString: string, chunk
|
||||
});
|
||||
}
|
||||
|
||||
let buffer = new Buffer(maximumBytesToRead);
|
||||
let buffer = Buffer.allocUnsafe(maximumBytesToRead);
|
||||
let offset = 0;
|
||||
|
||||
function readChunk(): void {
|
||||
|
||||
+44
-4
@@ -10,7 +10,7 @@ import { Readable } from 'stream';
|
||||
import { nfcall, ninvoke, SimpleThrottler } from 'vs/base/common/async';
|
||||
import { mkdirp, rimraf } from 'vs/base/node/pfs';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { open as openZip, Entry, ZipFile } from 'yauzl';
|
||||
import { open as _openZip, Entry, ZipFile } from 'yauzl';
|
||||
|
||||
export interface IExtractOptions {
|
||||
overwrite?: boolean;
|
||||
@@ -26,6 +26,29 @@ interface IOptions {
|
||||
sourcePathRegex: RegExp;
|
||||
}
|
||||
|
||||
export enum ExtractErrorType {
|
||||
Undefined,
|
||||
CorruptZip
|
||||
}
|
||||
|
||||
export class ExtractError extends Error {
|
||||
|
||||
readonly type: ExtractErrorType;
|
||||
readonly cause: Error;
|
||||
|
||||
constructor(type: ExtractErrorType, cause: Error) {
|
||||
let message = cause.message;
|
||||
|
||||
switch (type) {
|
||||
case ExtractErrorType.CorruptZip: message = `Corrupt ZIP: ${message}`; break;
|
||||
}
|
||||
|
||||
super(message);
|
||||
this.type = type;
|
||||
this.cause = cause;
|
||||
}
|
||||
}
|
||||
|
||||
function modeFromEntry(entry: Entry) {
|
||||
let attr = entry.externalFileAttributes >> 16 || 33188;
|
||||
|
||||
@@ -34,6 +57,18 @@ function modeFromEntry(entry: Entry) {
|
||||
.reduce((a, b) => a + b, attr & 61440 /* S_IFMT */);
|
||||
}
|
||||
|
||||
function toExtractError(err: Error): ExtractError {
|
||||
let type = ExtractErrorType.CorruptZip;
|
||||
|
||||
console.log('WHAT');
|
||||
|
||||
if (/end of central directory record signature not found/.test(err.message)) {
|
||||
type = ExtractErrorType.CorruptZip;
|
||||
}
|
||||
|
||||
return new ExtractError(type, err);
|
||||
}
|
||||
|
||||
function extractEntry(stream: Readable, fileName: string, mode: number, targetPath: string, options: IOptions): TPromise<void> {
|
||||
const dirName = path.dirname(fileName);
|
||||
const targetDirName = path.join(targetPath, dirName);
|
||||
@@ -74,13 +109,18 @@ function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions): TP
|
||||
|
||||
last = throttler.queue(() => stream.then(stream => extractEntry(stream, fileName, mode, targetPath, options)));
|
||||
});
|
||||
});
|
||||
}).then(null, err => TPromise.wrapError(toExtractError(err)));
|
||||
}
|
||||
|
||||
function openZip(zipFile: string): TPromise<ZipFile> {
|
||||
return nfcall<ZipFile>(_openZip, zipFile)
|
||||
.then(null, err => TPromise.wrapError(toExtractError(err)));
|
||||
}
|
||||
|
||||
export function extract(zipPath: string, targetPath: string, options: IExtractOptions = {}): TPromise<void> {
|
||||
const sourcePathRegex = new RegExp(options.sourcePath ? `^${options.sourcePath}` : '');
|
||||
|
||||
let promise = nfcall<ZipFile>(openZip, zipPath);
|
||||
let promise = openZip(zipPath);
|
||||
|
||||
if (options.overwrite) {
|
||||
promise = promise.then(zipfile => rimraf(targetPath).then(() => zipfile));
|
||||
@@ -90,7 +130,7 @@ export function extract(zipPath: string, targetPath: string, options: IExtractOp
|
||||
}
|
||||
|
||||
function read(zipPath: string, filePath: string): TPromise<Readable> {
|
||||
return nfcall(openZip, zipPath).then((zipfile: ZipFile) => {
|
||||
return openZip(zipPath).then(zipfile => {
|
||||
return new TPromise<Readable>((c, e) => {
|
||||
zipfile.on('entry', (entry: Entry) => {
|
||||
if (entry.fileName === filePath) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import types = require('vs/base/common/types');
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree';
|
||||
import { IconLabel, IIconLabelOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { IQuickNavigateConfiguration, IModel, IDataSource, IFilter, IAccessiblityProvider, IRenderer, IRunner, Mode } from 'vs/base/parts/quickopen/common/quickOpen';
|
||||
import { Action, IAction, IActionRunner } from 'vs/base/common/actions';
|
||||
import { compareAnything } from 'vs/base/common/comparers';
|
||||
@@ -84,7 +84,7 @@ export class QuickOpenEntry {
|
||||
/**
|
||||
* The options for the label to use for this entry
|
||||
*/
|
||||
public getLabelOptions(): IIconLabelOptions {
|
||||
public getLabelOptions(): IIconLabelValueOptions {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -116,6 +116,20 @@ export class QuickOpenEntry {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A tooltip to show when hovering over the entry.
|
||||
*/
|
||||
public getTooltip(): string {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A tooltip to show when hovering over the description portion of the entry.
|
||||
*/
|
||||
public getDescriptionTooltip(): string {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* An optional keybinding to show for an entry.
|
||||
*/
|
||||
@@ -171,8 +185,13 @@ export class QuickOpenEntry {
|
||||
return false;
|
||||
}
|
||||
|
||||
public isFile(): boolean {
|
||||
return false; // TODO@Ben debt with editor history merging
|
||||
/**
|
||||
* Determines if this quick open entry should merge with the editor history in quick open. If set to true
|
||||
* and the resource of this entry is the same as the resource for an editor history, it will not show up
|
||||
* because it is considered to be a duplicate of an editor history.
|
||||
*/
|
||||
public mergeWithEditorHistory(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +234,7 @@ export class QuickOpenEntryGroup extends QuickOpenEntry {
|
||||
return this.entry ? this.entry.getLabel() : super.getLabel();
|
||||
}
|
||||
|
||||
public getLabelOptions(): IIconLabelOptions {
|
||||
public getLabelOptions(): IIconLabelValueOptions {
|
||||
return this.entry ? this.entry.getLabelOptions() : super.getLabelOptions();
|
||||
}
|
||||
|
||||
@@ -293,7 +312,6 @@ export interface IQuickOpenEntryTemplateData {
|
||||
icon: HTMLSpanElement;
|
||||
label: IconLabel;
|
||||
detail: HighlightedLabel;
|
||||
description: HighlightedLabel;
|
||||
keybinding: KeybindingLabel;
|
||||
actionBar: ActionBar;
|
||||
}
|
||||
@@ -347,13 +365,7 @@ class Renderer implements IRenderer<QuickOpenEntry> {
|
||||
row1.appendChild(icon);
|
||||
|
||||
// Label
|
||||
const label = new IconLabel(row1, { supportHighlights: true });
|
||||
|
||||
// Description
|
||||
const descriptionContainer = document.createElement('span');
|
||||
row1.appendChild(descriptionContainer);
|
||||
DOM.addClass(descriptionContainer, 'quick-open-entry-description');
|
||||
const description = new HighlightedLabel(descriptionContainer);
|
||||
const label = new IconLabel(row1, { supportHighlights: true, supportDescriptionHighlights: true });
|
||||
|
||||
// Keybinding
|
||||
const keybindingContainer = document.createElement('span');
|
||||
@@ -392,15 +404,13 @@ class Renderer implements IRenderer<QuickOpenEntry> {
|
||||
icon,
|
||||
label,
|
||||
detail,
|
||||
description,
|
||||
keybinding,
|
||||
group,
|
||||
actionBar
|
||||
};
|
||||
}
|
||||
|
||||
public renderElement(entry: QuickOpenEntry, templateId: string, templateData: any, styles: IQuickOpenStyles): void {
|
||||
const data: IQuickOpenEntryTemplateData = templateData;
|
||||
public renderElement(entry: QuickOpenEntry, templateId: string, data: IQuickOpenEntryGroupTemplateData, styles: IQuickOpenStyles): void {
|
||||
|
||||
// Action Bar
|
||||
if (this.actionProvider.hasActions(null, entry)) {
|
||||
@@ -412,8 +422,6 @@ class Renderer implements IRenderer<QuickOpenEntry> {
|
||||
data.actionBar.context = entry; // make sure the context is the current element
|
||||
|
||||
this.actionProvider.getActions(null, entry).then((actions) => {
|
||||
// TODO@Ben this will not work anymore as soon as quick open has more actions
|
||||
// but as long as there is only one are ok
|
||||
if (data.actionBar.isEmpty() && actions && actions.length > 0) {
|
||||
data.actionBar.push(actions, { icon: true, label: false });
|
||||
} else if (!data.actionBar.isEmpty() && (!actions || actions.length === 0)) {
|
||||
@@ -431,7 +439,7 @@ class Renderer implements IRenderer<QuickOpenEntry> {
|
||||
// Entry group
|
||||
if (entry instanceof QuickOpenEntryGroup) {
|
||||
const group = <QuickOpenEntryGroup>entry;
|
||||
const groupData = <IQuickOpenEntryGroupTemplateData>templateData;
|
||||
const groupData = data;
|
||||
|
||||
// Border
|
||||
if (group.showBorder()) {
|
||||
@@ -457,30 +465,27 @@ class Renderer implements IRenderer<QuickOpenEntry> {
|
||||
data.icon.className = iconClass;
|
||||
|
||||
// Label
|
||||
const options: IIconLabelOptions = entry.getLabelOptions() || Object.create(null);
|
||||
const options: IIconLabelValueOptions = entry.getLabelOptions() || Object.create(null);
|
||||
options.matches = labelHighlights || [];
|
||||
data.label.setValue(entry.getLabel(), null, options);
|
||||
options.title = entry.getTooltip();
|
||||
options.descriptionTitle = entry.getDescriptionTooltip() || entry.getDescription(); // tooltip over description because it could overflow
|
||||
options.descriptionMatches = descriptionHighlights || [];
|
||||
data.label.setValue(entry.getLabel(), entry.getDescription(), options);
|
||||
|
||||
// Meta
|
||||
data.detail.set(entry.getDetail(), detailHighlights);
|
||||
|
||||
// Description
|
||||
data.description.set(entry.getDescription(), descriptionHighlights || []);
|
||||
data.description.element.title = entry.getDescription();
|
||||
|
||||
// Keybinding
|
||||
data.keybinding.set(entry.getKeybinding(), null);
|
||||
}
|
||||
}
|
||||
|
||||
public disposeTemplate(templateId: string, templateData: any): void {
|
||||
public disposeTemplate(templateId: string, templateData: IQuickOpenEntryGroupTemplateData): void {
|
||||
const data = templateData as IQuickOpenEntryGroupTemplateData;
|
||||
data.actionBar.dispose();
|
||||
data.actionBar = null;
|
||||
data.container = null;
|
||||
data.entry = null;
|
||||
data.description.dispose();
|
||||
data.description = null;
|
||||
data.keybinding.dispose();
|
||||
data.keybinding = null;
|
||||
data.detail.dispose();
|
||||
|
||||
@@ -26,6 +26,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
|
||||
export interface IQuickOpenCallbacks {
|
||||
onOk: () => void;
|
||||
@@ -154,8 +155,8 @@ export class QuickOpenWidget implements IModelProvider {
|
||||
}
|
||||
})
|
||||
.on(DOM.EventType.CONTEXT_MENU, (e: Event) => DOM.EventHelper.stop(e, true)) // Do this to fix an issue on Mac where the menu goes into the way
|
||||
.on(DOM.EventType.FOCUS, (e: Event) => this.gainingFocus(), null, true)
|
||||
.on(DOM.EventType.BLUR, (e: Event) => this.loosingFocus(e), null, true);
|
||||
.on(DOM.EventType.FOCUS, (e: FocusEvent) => this.gainingFocus(), null, true)
|
||||
.on(DOM.EventType.BLUR, (e: FocusEvent) => this.loosingFocus(e), null, true);
|
||||
|
||||
// Progress Bar
|
||||
this.progressBar = new ProgressBar(div.clone(), { progressBarBackground: this.styles.progressBarBackground });
|
||||
@@ -253,7 +254,10 @@ export class QuickOpenWidget implements IModelProvider {
|
||||
|
||||
this.toUnbind.push(this.tree.onDidChangeSelection((event: ISelectionEvent) => {
|
||||
if (event.selection && event.selection.length > 0) {
|
||||
this.elementSelected(event.selection[0], event);
|
||||
const mouseEvent: StandardMouseEvent = event.payload && event.payload.originalEvent instanceof StandardMouseEvent ? event.payload.originalEvent : void 0;
|
||||
const shouldOpenInBackground = mouseEvent ? this.shouldOpenInBackground(mouseEvent) : false;
|
||||
|
||||
this.elementSelected(event.selection[0], event, shouldOpenInBackground ? Mode.OPEN_IN_BACKGROUND : Mode.OPEN);
|
||||
}
|
||||
}));
|
||||
}).
|
||||
@@ -399,19 +403,26 @@ export class QuickOpenWidget implements IModelProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private shouldOpenInBackground(e: StandardKeyboardEvent): boolean {
|
||||
if (e.keyCode !== KeyCode.RightArrow) {
|
||||
return false; // only for right arrow
|
||||
private shouldOpenInBackground(e: StandardKeyboardEvent | StandardMouseEvent): boolean {
|
||||
|
||||
// Keyboard
|
||||
if (e instanceof StandardKeyboardEvent) {
|
||||
if (e.keyCode !== KeyCode.RightArrow) {
|
||||
return false; // only for right arrow
|
||||
}
|
||||
|
||||
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
|
||||
return false; // no modifiers allowed
|
||||
}
|
||||
|
||||
// validate the cursor is at the end of the input and there is no selection,
|
||||
// and if not prevent opening in the background such as the selection can be changed
|
||||
const element = this.inputBox.inputElement;
|
||||
return element.selectionEnd === this.inputBox.value.length && element.selectionStart === element.selectionEnd;
|
||||
}
|
||||
|
||||
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
|
||||
return false; // no modifiers allowed
|
||||
}
|
||||
|
||||
// validate the cursor is at the end of the input and there is no selection,
|
||||
// and if not prevent opening in the background such as the selection can be changed
|
||||
const element = this.inputBox.inputElement;
|
||||
return element.selectionEnd === this.inputBox.value.length && element.selectionStart === element.selectionEnd;
|
||||
// Mouse
|
||||
return e.middleButton;
|
||||
}
|
||||
|
||||
private onType(): void {
|
||||
@@ -931,12 +942,12 @@ export class QuickOpenWidget implements IModelProvider {
|
||||
this.isLoosingFocus = false;
|
||||
}
|
||||
|
||||
private loosingFocus(e: Event): void {
|
||||
private loosingFocus(e: FocusEvent): void {
|
||||
if (!this.isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const relatedTarget = (<any>e).relatedTarget;
|
||||
const relatedTarget = e.relatedTarget as HTMLElement;
|
||||
if (!this.quickNavigateConfiguration && DOM.isAncestor(relatedTarget, this.builder.getHTMLElement())) {
|
||||
return; // user clicked somewhere into quick open widget, do not close thereby
|
||||
}
|
||||
|
||||
@@ -70,6 +70,11 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.quick-open-widget .quick-open-tree .monaco-icon-label,
|
||||
.quick-open-widget .quick-open-tree .monaco-icon-label .monaco-icon-label-description-container {
|
||||
flex: 1; /* make sure the icon label grows within the row */
|
||||
}
|
||||
|
||||
.quick-open-widget .quick-open-tree .quick-open-entry .monaco-highlighted-label span {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -79,15 +84,6 @@
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.quick-open-widget .quick-open-tree .quick-open-entry-description {
|
||||
opacity: 0.7;
|
||||
margin-left: 0.5em;
|
||||
font-size: 0.9em;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.quick-open-widget .quick-open-tree .content.has-group-label .quick-open-entry-keybinding {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
@@ -87,10 +87,10 @@ function doScore(query: string, queryLower: string, queryLength: number, target:
|
||||
const leftIndex = currentIndex - 1;
|
||||
const diagIndex = (queryIndex - 1) * targetLength + targetIndex - 1;
|
||||
|
||||
const leftScore = targetIndex > 0 ? scores[leftIndex] : 0;
|
||||
const diagScore = queryIndex > 0 && targetIndex > 0 ? scores[diagIndex] : 0;
|
||||
const leftScore: number = targetIndex > 0 ? scores[leftIndex] : 0;
|
||||
const diagScore: number = queryIndex > 0 && targetIndex > 0 ? scores[diagIndex] : 0;
|
||||
|
||||
const matchesSequenceLength = queryIndex > 0 && targetIndex > 0 ? matches[diagIndex] : 0;
|
||||
const matchesSequenceLength: number = queryIndex > 0 && targetIndex > 0 ? matches[diagIndex] : 0;
|
||||
|
||||
// If we are not matching on the first query character any more, we only produce a
|
||||
// score if we had a score previously for the last query index (by looking at the diagScore).
|
||||
@@ -296,6 +296,7 @@ const LABEL_CAMELCASE_SCORE = 1 << 16;
|
||||
const LABEL_SCORE_THRESHOLD = 1 << 15;
|
||||
|
||||
export interface IPreparedQuery {
|
||||
original: string;
|
||||
value: string;
|
||||
lowercase: string;
|
||||
containsPathSeparator: boolean;
|
||||
@@ -304,12 +305,13 @@ export interface IPreparedQuery {
|
||||
/**
|
||||
* Helper function to prepare a search value for scoring in quick open by removing unwanted characters.
|
||||
*/
|
||||
export function prepareQuery(value: string): IPreparedQuery {
|
||||
export function prepareQuery(original: string): IPreparedQuery {
|
||||
let lowercase: string;
|
||||
let containsPathSeparator: boolean;
|
||||
let value: string;
|
||||
|
||||
if (value) {
|
||||
value = stripWildcards(value).replace(/\s/g, ''); // get rid of all wildcards and whitespace
|
||||
if (original) {
|
||||
value = stripWildcards(original).replace(/\s/g, ''); // get rid of all wildcards and whitespace
|
||||
if (isWindows) {
|
||||
value = value.replace(/\//g, '\\'); // Help Windows users to search for paths when using slash
|
||||
}
|
||||
@@ -318,7 +320,7 @@ export function prepareQuery(value: string): IPreparedQuery {
|
||||
containsPathSeparator = value.indexOf(nativeSep) >= 0;
|
||||
}
|
||||
|
||||
return { value, lowercase, containsPathSeparator };
|
||||
return { original, value, lowercase, containsPathSeparator };
|
||||
}
|
||||
|
||||
export function scoreItem<T>(item: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor<T>, cache: ScorerCache): IItemScore {
|
||||
@@ -354,7 +356,7 @@ export function scoreItem<T>(item: T, query: IPreparedQuery, fuzzy: boolean, acc
|
||||
function doScoreItem(label: string, description: string, path: string, query: IPreparedQuery, fuzzy: boolean): IItemScore {
|
||||
|
||||
// 1.) treat identity matches on full path highest
|
||||
if (path && isEqual(query.value, path, true)) {
|
||||
if (path && isEqual(query.original, path, true)) {
|
||||
return { score: PATH_IDENTITY_SCORE, labelMatch: [{ start: 0, end: label.length }], descriptionMatch: description ? [{ start: 0, end: description.length }] : void 0 };
|
||||
}
|
||||
|
||||
|
||||
@@ -38,8 +38,14 @@ export enum ClickBehavior {
|
||||
ON_MOUSE_UP
|
||||
}
|
||||
|
||||
export enum OpenMode {
|
||||
SINGLE_CLICK,
|
||||
DOUBLE_CLICK
|
||||
}
|
||||
|
||||
export interface IControllerOptions {
|
||||
clickBehavior?: ClickBehavior;
|
||||
openMode?: OpenMode;
|
||||
keyboardSupport?: boolean;
|
||||
}
|
||||
|
||||
@@ -82,7 +88,7 @@ export class DefaultController implements _.IController {
|
||||
|
||||
private options: IControllerOptions;
|
||||
|
||||
constructor(options: IControllerOptions = { clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: true }) {
|
||||
constructor(options: IControllerOptions = { clickBehavior: ClickBehavior.ON_MOUSE_DOWN, keyboardSupport: true, openMode: OpenMode.SINGLE_CLICK }) {
|
||||
this.options = options;
|
||||
|
||||
this.downKeyBindingDispatcher = new KeybindingDispatcher();
|
||||
@@ -153,12 +159,14 @@ export class DefaultController implements _.IController {
|
||||
|
||||
protected onLeftClick(tree: _.ITree, element: any, eventish: ICancelableEvent, origin: string = 'mouse'): boolean {
|
||||
const payload = { origin: origin, originalEvent: eventish };
|
||||
const event = <mouse.IMouseEvent>eventish;
|
||||
const isDoubleClick = (origin === 'mouse' && event.detail === 2);
|
||||
|
||||
if (tree.getInput() === element) {
|
||||
tree.clearFocus(payload);
|
||||
tree.clearSelection(payload);
|
||||
} else {
|
||||
const isMouseDown = eventish && (<mouse.IMouseEvent>eventish).browserEvent && (<mouse.IMouseEvent>eventish).browserEvent.type === 'mousedown';
|
||||
const isMouseDown = eventish && event.browserEvent && event.browserEvent.type === 'mousedown';
|
||||
if (!isMouseDown) {
|
||||
eventish.preventDefault(); // we cannot preventDefault onMouseDown because this would break DND otherwise
|
||||
}
|
||||
@@ -168,16 +176,36 @@ export class DefaultController implements _.IController {
|
||||
tree.setSelection([element], payload);
|
||||
tree.setFocus(element, payload);
|
||||
|
||||
if (tree.isExpanded(element)) {
|
||||
tree.collapse(element).done(null, errors.onUnexpectedError);
|
||||
} else {
|
||||
tree.expand(element).done(null, errors.onUnexpectedError);
|
||||
if (this.openOnSingleClick || isDoubleClick || this.isClickOnTwistie(event)) {
|
||||
if (tree.isExpanded(element)) {
|
||||
tree.collapse(element).done(null, errors.onUnexpectedError);
|
||||
} else {
|
||||
tree.expand(element).done(null, errors.onUnexpectedError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected setOpenMode(openMode: OpenMode) {
|
||||
this.options.openMode = openMode;
|
||||
}
|
||||
|
||||
protected get openOnSingleClick(): boolean {
|
||||
return this.options.openMode === OpenMode.SINGLE_CLICK;
|
||||
}
|
||||
|
||||
protected isClickOnTwistie(event: mouse.IMouseEvent): boolean {
|
||||
const target = event.target as HTMLElement;
|
||||
|
||||
// There is no way to find out if the ::before element is clicked where
|
||||
// the twistie is drawn, but the <div class="content"> element in the
|
||||
// tree item is the only thing we get back as target when the user clicks
|
||||
// on the twistie.
|
||||
return target && target.className === 'content' && dom.hasClass(target.parentElement, 'monaco-tree-row');
|
||||
}
|
||||
|
||||
public onContextMenu(tree: _.ITree, element: any, event: _.ContextMenuEvent): boolean {
|
||||
if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') {
|
||||
return false; // allow context menu on input fields
|
||||
|
||||
@@ -6,10 +6,6 @@
|
||||
|
||||
import _ = require('vs/base/parts/tree/browser/tree');
|
||||
import Mouse = require('vs/base/browser/mouseEvent');
|
||||
import { DefaultDragAndDrop } from 'vs/base/parts/tree/browser/treeDefaults';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { basename } from 'vs/base/common/paths';
|
||||
import { getPathLabel } from 'vs/base/common/labels';
|
||||
|
||||
export class ElementsDragAndDropData implements _.IDragAndDropData {
|
||||
|
||||
@@ -75,48 +71,4 @@ export class DesktopDragAndDropData implements _.IDragAndDropData {
|
||||
files: this.files
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class SimpleFileResourceDragAndDrop extends DefaultDragAndDrop {
|
||||
|
||||
constructor(private toResource: (obj: any) => URI) {
|
||||
super();
|
||||
}
|
||||
|
||||
public getDragURI(tree: _.ITree, obj: any): string {
|
||||
const resource = this.toResource(obj);
|
||||
if (resource) {
|
||||
return resource.toString();
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
public getDragLabel(tree: _.ITree, elements: any[]): string {
|
||||
if (elements.length > 1) {
|
||||
return String(elements.length);
|
||||
}
|
||||
|
||||
const resource = this.toResource(elements[0]);
|
||||
if (resource) {
|
||||
return basename(resource.fsPath);
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
public onDragStart(tree: _.ITree, data: _.IDragAndDropData, originalEvent: Mouse.DragMouseEvent): void {
|
||||
const sources: object[] = data.getData();
|
||||
|
||||
let source: object = null;
|
||||
if (sources.length > 0) {
|
||||
source = sources[0];
|
||||
}
|
||||
|
||||
// Apply some datatransfer types to allow for dragging the element outside of the application
|
||||
const resource = this.toResource(source);
|
||||
if (resource) {
|
||||
originalEvent.dataTransfer.setData('text/plain', getPathLabel(resource));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import _ = require('vs/base/parts/tree/browser/tree');
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IDomNodePagePosition } from 'vs/base/browser/dom';
|
||||
import { DataTransfers } from 'vs/base/browser/dnd';
|
||||
|
||||
export interface IRow {
|
||||
element: HTMLElement;
|
||||
@@ -825,7 +826,7 @@ export class TreeView extends HeightMap {
|
||||
|
||||
public getScrollPosition(): number {
|
||||
const height = this.getTotalHeight() - this.viewHeight;
|
||||
return height <= 0 ? 0 : this.scrollTop / height;
|
||||
return height <= 0 ? 1 : this.scrollTop / height;
|
||||
}
|
||||
|
||||
public setScrollPosition(pos: number): void {
|
||||
@@ -1291,7 +1292,7 @@ export class TreeView extends HeightMap {
|
||||
}
|
||||
|
||||
e.dataTransfer.effectAllowed = 'copyMove';
|
||||
e.dataTransfer.setData('URL', item.uri);
|
||||
e.dataTransfer.setData(DataTransfers.RESOURCES, JSON.stringify([item.uri]));
|
||||
if (e.dataTransfer.setDragImage) {
|
||||
let label: string;
|
||||
|
||||
@@ -1658,4 +1659,4 @@ export class TreeView extends HeightMap {
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,6 @@ function select(builder: Builder, selector: string, offdom?: boolean): MultiBuil
|
||||
}
|
||||
|
||||
suite('Builder', () => {
|
||||
|
||||
test('Binding', function () {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -11,4 +11,32 @@ const $ = dom.$;
|
||||
suite('dom', () => {
|
||||
test('hasClass', () => {
|
||||
});
|
||||
|
||||
suite('$', () => {
|
||||
test('should build simple nodes', () => {
|
||||
const div = $('div');
|
||||
assert(div);
|
||||
assert(div instanceof HTMLElement);
|
||||
assert.equal(div.tagName, 'DIV');
|
||||
assert(!div.firstChild);
|
||||
});
|
||||
|
||||
test('should build nodes with attributes', () => {
|
||||
let div = $('div', { class: 'test' });
|
||||
assert.equal(div.className, 'test');
|
||||
|
||||
div = $('div', null);
|
||||
assert.equal(div.className, '');
|
||||
});
|
||||
|
||||
test('should build nodes with children', () => {
|
||||
let div = $('div', null, $('span', { id: 'demospan' }));
|
||||
let firstChild = div.firstChild as HTMLElement;
|
||||
assert.equal(firstChild.tagName, 'SPAN');
|
||||
assert.equal(firstChild.id, 'demospan');
|
||||
|
||||
div = $('div', null, 'hello');
|
||||
assert.equal(div.firstChild.textContent, 'hello');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,9 +52,12 @@ suite('HtmlContent', () => {
|
||||
test('action', () => {
|
||||
var callbackCalled = false;
|
||||
var result: HTMLElement = renderFormattedText('[[action]]', {
|
||||
actionCallback(content) {
|
||||
assert.strictEqual(content, '0');
|
||||
callbackCalled = true;
|
||||
actionHandler: {
|
||||
callback(content) {
|
||||
assert.strictEqual(content, '0');
|
||||
callbackCalled = true;
|
||||
},
|
||||
disposeables: []
|
||||
}
|
||||
});
|
||||
assert.strictEqual(result.innerHTML, '<a href="#">action</a>');
|
||||
@@ -68,9 +71,12 @@ suite('HtmlContent', () => {
|
||||
test('fancy action', () => {
|
||||
var callbackCalled = false;
|
||||
var result: HTMLElement = renderFormattedText('__**[[action]]**__', {
|
||||
actionCallback(content) {
|
||||
assert.strictEqual(content, '0');
|
||||
callbackCalled = true;
|
||||
actionHandler: {
|
||||
callback(content) {
|
||||
assert.strictEqual(content, '0');
|
||||
callbackCalled = true;
|
||||
},
|
||||
disposeables: []
|
||||
}
|
||||
});
|
||||
assert.strictEqual(result.innerHTML, '<i><b><a href="#">action</a></b></i>');
|
||||
|
||||
@@ -63,232 +63,6 @@ function getSashes(splitview: SplitView): Sash[] {
|
||||
}
|
||||
|
||||
suite('Splitview', () => {
|
||||
let container: HTMLElement;
|
||||
|
||||
setup(() => {
|
||||
container = document.createElement('div');
|
||||
container.style.position = 'absolute';
|
||||
container.style.width = `${200}px`;
|
||||
container.style.height = `${200}px`;
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
container = null;
|
||||
});
|
||||
|
||||
test('empty splitview has empty DOM', () => {
|
||||
});
|
||||
|
||||
test('calls view methods on addView and removeView', () => {
|
||||
const view = new TestView(20, 20);
|
||||
const splitview = new SplitView(container);
|
||||
|
||||
let didLayout = false;
|
||||
const layoutDisposable = view.onDidLayout(() => didLayout = true);
|
||||
|
||||
let didRender = false;
|
||||
const renderDisposable = view.onDidRender(() => didRender = true);
|
||||
|
||||
splitview.addView(view, 20);
|
||||
|
||||
assert.equal(view.size, 20, 'view has right size');
|
||||
assert(didLayout, 'layout is called');
|
||||
assert(didLayout, 'render is called');
|
||||
|
||||
splitview.dispose();
|
||||
layoutDisposable.dispose();
|
||||
renderDisposable.dispose();
|
||||
view.dispose();
|
||||
});
|
||||
|
||||
test('stretches view to viewport', () => {
|
||||
const view = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const splitview = new SplitView(container);
|
||||
splitview.layout(200);
|
||||
|
||||
splitview.addView(view, 20);
|
||||
assert.equal(view.size, 200, 'view is stretched');
|
||||
|
||||
splitview.layout(200);
|
||||
assert.equal(view.size, 200, 'view stayed the same');
|
||||
|
||||
splitview.layout(100);
|
||||
assert.equal(view.size, 100, 'view is collapsed');
|
||||
|
||||
splitview.layout(20);
|
||||
assert.equal(view.size, 20, 'view is collapsed');
|
||||
|
||||
splitview.layout(10);
|
||||
assert.equal(view.size, 20, 'view is clamped');
|
||||
|
||||
splitview.layout(200);
|
||||
assert.equal(view.size, 200, 'view is stretched');
|
||||
|
||||
splitview.dispose();
|
||||
view.dispose();
|
||||
});
|
||||
|
||||
test('can resize views', () => {
|
||||
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const view3 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const splitview = new SplitView(container);
|
||||
splitview.layout(200);
|
||||
|
||||
splitview.addView(view1, 20);
|
||||
splitview.addView(view2, 20);
|
||||
splitview.addView(view3, 20);
|
||||
|
||||
assert.equal(view1.size, 160, 'view1 is stretched');
|
||||
assert.equal(view2.size, 20, 'view2 size is 20');
|
||||
assert.equal(view3.size, 20, 'view3 size is 20');
|
||||
|
||||
splitview.resizeView(1, 40);
|
||||
|
||||
assert.equal(view1.size, 140, 'view1 is collapsed');
|
||||
assert.equal(view2.size, 40, 'view2 is stretched');
|
||||
assert.equal(view3.size, 20, 'view3 stays the same');
|
||||
|
||||
splitview.resizeView(0, 70);
|
||||
|
||||
assert.equal(view1.size, 70, 'view1 is collapsed');
|
||||
assert.equal(view2.size, 110, 'view2 is expanded');
|
||||
assert.equal(view3.size, 20, 'view3 stays the same');
|
||||
|
||||
splitview.resizeView(2, 40);
|
||||
|
||||
assert.equal(view1.size, 70, 'view1 stays the same');
|
||||
assert.equal(view2.size, 90, 'view2 is collapsed');
|
||||
assert.equal(view3.size, 40, 'view3 is stretched');
|
||||
|
||||
splitview.dispose();
|
||||
view3.dispose();
|
||||
view2.dispose();
|
||||
view1.dispose();
|
||||
});
|
||||
|
||||
test('reacts to view changes', () => {
|
||||
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const view3 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const splitview = new SplitView(container);
|
||||
splitview.layout(200);
|
||||
|
||||
splitview.addView(view1, 20);
|
||||
splitview.addView(view2, 20);
|
||||
splitview.addView(view3, 20);
|
||||
|
||||
assert.equal(view1.size, 160, 'view1 is stretched');
|
||||
assert.equal(view2.size, 20, 'view2 size is 20');
|
||||
assert.equal(view3.size, 20, 'view3 size is 20');
|
||||
|
||||
view1.maximumSize = 20;
|
||||
|
||||
assert.equal(view1.size, 20, 'view1 is collapsed');
|
||||
assert.equal(view2.size, 20, 'view2 stays the same');
|
||||
assert.equal(view3.size, 160, 'view3 is stretched');
|
||||
|
||||
view3.maximumSize = 40;
|
||||
|
||||
assert.equal(view1.size, 20, 'view1 stays the same');
|
||||
assert.equal(view2.size, 140, 'view2 is stretched');
|
||||
assert.equal(view3.size, 40, 'view3 is collapsed');
|
||||
|
||||
view2.maximumSize = 200;
|
||||
|
||||
assert.equal(view1.size, 20, 'view1 stays the same');
|
||||
assert.equal(view2.size, 140, 'view2 stays the same');
|
||||
assert.equal(view3.size, 40, 'view3 stays the same');
|
||||
|
||||
view3.maximumSize = Number.POSITIVE_INFINITY;
|
||||
view3.minimumSize = 100;
|
||||
|
||||
assert.equal(view1.size, 20, 'view1 is collapsed');
|
||||
assert.equal(view2.size, 80, 'view2 is collapsed');
|
||||
assert.equal(view3.size, 100, 'view3 is stretched');
|
||||
|
||||
splitview.dispose();
|
||||
view3.dispose();
|
||||
view2.dispose();
|
||||
view1.dispose();
|
||||
});
|
||||
|
||||
test('sashes are properly enabled/disabled', () => {
|
||||
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const view3 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const splitview = new SplitView(container);
|
||||
splitview.layout(200);
|
||||
|
||||
splitview.addView(view1, 20);
|
||||
splitview.addView(view2, 20);
|
||||
splitview.addView(view3, 20);
|
||||
|
||||
let sashes = getSashes(splitview);
|
||||
assert.equal(sashes.length, 2, 'there are two sashes');
|
||||
assert.equal(sashes[0].enabled, true, 'first sash is enabled');
|
||||
assert.equal(sashes[1].enabled, true, 'second sash is enabled');
|
||||
|
||||
splitview.layout(60);
|
||||
assert.equal(sashes[0].enabled, false, 'first sash is disabled');
|
||||
assert.equal(sashes[1].enabled, false, 'second sash is disabled');
|
||||
|
||||
splitview.layout(20);
|
||||
assert.equal(sashes[0].enabled, false, 'first sash is disabled');
|
||||
assert.equal(sashes[1].enabled, false, 'second sash is disabled');
|
||||
|
||||
splitview.layout(200);
|
||||
assert.equal(sashes[0].enabled, true, 'first sash is enabled');
|
||||
assert.equal(sashes[1].enabled, true, 'second sash is enabled');
|
||||
|
||||
view1.maximumSize = 20;
|
||||
assert.equal(sashes[0].enabled, false, 'first sash is disabled');
|
||||
assert.equal(sashes[1].enabled, true, 'second sash is enabled');
|
||||
|
||||
view2.maximumSize = 20;
|
||||
assert.equal(sashes[0].enabled, false, 'first sash is disabled');
|
||||
assert.equal(sashes[1].enabled, false, 'second sash is disabled');
|
||||
|
||||
view1.maximumSize = 300;
|
||||
assert.equal(sashes[0].enabled, true, 'first sash is enabled');
|
||||
assert.equal(sashes[1].enabled, true, 'second sash is enabled');
|
||||
|
||||
view2.maximumSize = 200;
|
||||
assert.equal(sashes[0].enabled, true, 'first sash is enabled');
|
||||
assert.equal(sashes[1].enabled, true, 'second sash is enabled');
|
||||
|
||||
splitview.dispose();
|
||||
view3.dispose();
|
||||
view2.dispose();
|
||||
view1.dispose();
|
||||
});
|
||||
|
||||
test('issue #35497', () => {
|
||||
const view1 = new TestView(160, Number.POSITIVE_INFINITY);
|
||||
const view2 = new TestView(66, 66);
|
||||
|
||||
const splitview = new SplitView(container);
|
||||
splitview.layout(986);
|
||||
|
||||
splitview.addView(view1, 142, 0);
|
||||
assert.equal(view1.size, 986, 'first view is stretched');
|
||||
|
||||
view2.onDidRender(() => {
|
||||
assert.throws(() => splitview.resizeView(1, 922));
|
||||
assert.throws(() => splitview.resizeView(1, 922));
|
||||
});
|
||||
|
||||
splitview.addView(view2, 66, 0);
|
||||
assert.equal(view2.size, 66, 'second view is fixed');
|
||||
assert.equal(view1.size, 986 - 66, 'first view is collapsed');
|
||||
|
||||
const viewContainers = container.querySelectorAll('.split-view-view');
|
||||
assert.equal(viewContainers.length, 2, 'there are two view containers');
|
||||
assert.equal((viewContainers.item(0) as HTMLElement).style.height, '66px', 'second view container is 66px');
|
||||
assert.equal((viewContainers.item(1) as HTMLElement).style.height, `${986 - 66}px`, 'first view container is 66px');
|
||||
|
||||
splitview.dispose();
|
||||
view2.dispose();
|
||||
view1.dispose();
|
||||
});
|
||||
});
|
||||
@@ -7,6 +7,7 @@
|
||||
import * as assert from 'assert';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import arrays = require('vs/base/common/arrays');
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
|
||||
suite('Arrays', () => {
|
||||
test('findFirst', function () {
|
||||
@@ -269,5 +270,41 @@ suite('Arrays', () => {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test('coalesce', function () {
|
||||
let a = coalesce([null, 1, null, 2, 3]);
|
||||
assert.equal(a.length, 3);
|
||||
assert.equal(a[0], 1);
|
||||
assert.equal(a[1], 2);
|
||||
assert.equal(a[2], 3);
|
||||
|
||||
coalesce([null, 1, null, void 0, undefined, 2, 3]);
|
||||
assert.equal(a.length, 3);
|
||||
assert.equal(a[0], 1);
|
||||
assert.equal(a[1], 2);
|
||||
assert.equal(a[2], 3);
|
||||
|
||||
let b = [];
|
||||
b[10] = 1;
|
||||
b[20] = 2;
|
||||
b[30] = 3;
|
||||
b = coalesce(b);
|
||||
assert.equal(b.length, 3);
|
||||
assert.equal(b[0], 1);
|
||||
assert.equal(b[1], 2);
|
||||
assert.equal(b[2], 3);
|
||||
|
||||
let sparse = [];
|
||||
sparse[0] = 1;
|
||||
sparse[1] = 1;
|
||||
sparse[17] = 1;
|
||||
sparse[1000] = 1;
|
||||
sparse[1001] = 1;
|
||||
|
||||
assert.equal(sparse.length, 1002);
|
||||
|
||||
sparse = coalesce(sparse);
|
||||
assert.equal(sparse.length, 5);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { LcsDiff, IDiffChange } from 'vs/base/common/diff/diff';
|
||||
import { LcsDiff2 } from 'vs/base/common/diff/diff2';
|
||||
|
||||
class StringDiffSequence {
|
||||
|
||||
@@ -116,11 +115,6 @@ suite('Diff', () => {
|
||||
this.timeout(10000);
|
||||
lcsTests(LcsDiff);
|
||||
});
|
||||
|
||||
test('LcsDiff2 - different strings tests', function () {
|
||||
this.timeout(10000);
|
||||
lcsTests(LcsDiff2);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Diff - Ported from VS', () => {
|
||||
|
||||
@@ -393,11 +393,11 @@ suite('Filters', () => {
|
||||
|
||||
// issue #17836
|
||||
// assertTopScore(fuzzyScore, 'TEdit', 1, 'TextEditorDecorationType', 'TextEdit', 'TextEditor');
|
||||
assertTopScore(fuzzyScore, 'p', 0, 'parse', 'posix', 'pafdsa', 'path', 'p');
|
||||
assertTopScore(fuzzyScore, 'p', 4, 'parse', 'posix', 'pafdsa', 'path', 'p');
|
||||
assertTopScore(fuzzyScore, 'pa', 0, 'parse', 'pafdsa', 'path');
|
||||
|
||||
// issue #14583
|
||||
assertTopScore(fuzzyScore, 'log', 3, 'HTMLOptGroupElement', 'ScrollLogicalPosition', 'SVGFEMorphologyElement', 'log');
|
||||
assertTopScore(fuzzyScore, 'log', 3, 'HTMLOptGroupElement', 'ScrollLogicalPosition', 'SVGFEMorphologyElement', 'log', 'logger');
|
||||
assertTopScore(fuzzyScore, 'e', 2, 'AbstractWorker', 'ActiveXObject', 'else');
|
||||
|
||||
// issue #14446
|
||||
@@ -415,6 +415,8 @@ suite('Filters', () => {
|
||||
assertTopScore(fuzzyScore, 'is', 0, 'isValidViewletId', 'import statement');
|
||||
|
||||
assertTopScore(fuzzyScore, 'title', 1, 'files.trimTrailingWhitespace', 'window.title');
|
||||
|
||||
assertTopScore(fuzzyScore, 'const', 1, 'constructor', 'const', 'cuOnstrul');
|
||||
});
|
||||
|
||||
test('Unexpected suggestion scoring, #28791', function () {
|
||||
|
||||
@@ -83,11 +83,11 @@ suite('History Navigator', () => {
|
||||
});
|
||||
|
||||
test('adding existing element changes the position', function () {
|
||||
let testObject = new HistoryNavigator(['1', '2', '3', '4'], 2);
|
||||
let testObject = new HistoryNavigator(['1', '2', '3', '4'], 5);
|
||||
|
||||
testObject.add('2');
|
||||
|
||||
assert.deepEqual(['4', '2'], toArray(testObject));
|
||||
assert.deepEqual(['1', '3', '4', '2'], toArray(testObject));
|
||||
});
|
||||
|
||||
test('add resets the navigator to last', function () {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as assert from 'assert';
|
||||
import { IMatch } from 'vs/base/common/filters';
|
||||
import { matchesFuzzyOcticonAware, parseOcticons } from 'vs/base/common/octicon';
|
||||
|
||||
export interface IOcticonFilter {
|
||||
// Returns null if word doesn't match.
|
||||
(query: string, target: { text: string, octiconOffsets?: number[] }): IMatch[];
|
||||
}
|
||||
|
||||
function filterOk(filter: IOcticonFilter, word: string, target: { text: string, octiconOffsets?: number[] }, highlights?: { start: number; end: number; }[]) {
|
||||
let r = filter(word, target);
|
||||
assert(r);
|
||||
if (highlights) {
|
||||
assert.deepEqual(r, highlights);
|
||||
}
|
||||
}
|
||||
|
||||
suite('Octicon', () => {
|
||||
test('matchesFuzzzyOcticonAware', function () {
|
||||
|
||||
// Camel Case
|
||||
|
||||
filterOk(matchesFuzzyOcticonAware, 'ccr', parseOcticons('$(octicon)CamelCaseRocks$(octicon)'), [
|
||||
{ start: 10, end: 11 },
|
||||
{ start: 15, end: 16 },
|
||||
{ start: 19, end: 20 }
|
||||
]);
|
||||
|
||||
filterOk(matchesFuzzyOcticonAware, 'ccr', parseOcticons('$(octicon) CamelCaseRocks $(octicon)'), [
|
||||
{ start: 11, end: 12 },
|
||||
{ start: 16, end: 17 },
|
||||
{ start: 20, end: 21 }
|
||||
]);
|
||||
|
||||
filterOk(matchesFuzzyOcticonAware, 'iut', parseOcticons('$(octicon) Indent $(octico) Using $(octic) Tpaces'), [
|
||||
{ start: 11, end: 12 },
|
||||
{ start: 28, end: 29 },
|
||||
{ start: 43, end: 44 },
|
||||
]);
|
||||
|
||||
// Prefix
|
||||
|
||||
filterOk(matchesFuzzyOcticonAware, 'using', parseOcticons('$(octicon) Indent Using Spaces'), [
|
||||
{ start: 18, end: 23 },
|
||||
]);
|
||||
|
||||
// Broken Octicon
|
||||
|
||||
filterOk(matchesFuzzyOcticonAware, 'octicon', parseOcticons('This $(octicon Indent Using Spaces'), [
|
||||
{ start: 7, end: 14 },
|
||||
]);
|
||||
|
||||
filterOk(matchesFuzzyOcticonAware, 'indent', parseOcticons('This $octicon Indent Using Spaces'), [
|
||||
{ start: 14, end: 20 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as assert from 'assert';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { distinctParents, dirname } from 'vs/base/common/resources';
|
||||
import { normalize } from 'vs/base/common/paths';
|
||||
|
||||
suite('Resources', () => {
|
||||
|
||||
test('distinctParents', () => {
|
||||
|
||||
// Basic
|
||||
let resources = [
|
||||
URI.file('/some/folderA/file.txt'),
|
||||
URI.file('/some/folderB/file.txt'),
|
||||
URI.file('/some/folderC/file.txt')
|
||||
];
|
||||
|
||||
let distinct = distinctParents(resources, r => r);
|
||||
assert.equal(distinct.length, 3);
|
||||
assert.equal(distinct[0].toString(), resources[0].toString());
|
||||
assert.equal(distinct[1].toString(), resources[1].toString());
|
||||
assert.equal(distinct[2].toString(), resources[2].toString());
|
||||
|
||||
// Parent / Child
|
||||
resources = [
|
||||
URI.file('/some/folderA'),
|
||||
URI.file('/some/folderA/file.txt'),
|
||||
URI.file('/some/folderA/child/file.txt'),
|
||||
URI.file('/some/folderA2/file.txt'),
|
||||
URI.file('/some/file.txt')
|
||||
];
|
||||
|
||||
distinct = distinctParents(resources, r => r);
|
||||
assert.equal(distinct.length, 3);
|
||||
assert.equal(distinct[0].toString(), resources[0].toString());
|
||||
assert.equal(distinct[1].toString(), resources[3].toString());
|
||||
assert.equal(distinct[2].toString(), resources[4].toString());
|
||||
});
|
||||
|
||||
test('dirname', (done) => {
|
||||
const f = URI.file('/some/file/test.txt');
|
||||
const d = dirname(f);
|
||||
assert.equal(d.fsPath, normalize('/some/file', true));
|
||||
|
||||
// does not explode (https://github.com/Microsoft/vscode/issues/41987)
|
||||
dirname(URI.from({ scheme: 'file', authority: '/users/someone/portal.h' }));
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -348,4 +348,37 @@ suite('Strings', () => {
|
||||
assert.equal(strings.stripUTF8BOM('abc'), 'abc');
|
||||
assert.equal(strings.stripUTF8BOM(''), '');
|
||||
});
|
||||
|
||||
test('containsUppercaseCharacter', () => {
|
||||
[
|
||||
[null, false],
|
||||
['', false],
|
||||
['foo', false],
|
||||
['föö', false],
|
||||
['ناك', false],
|
||||
['מבוססת', false],
|
||||
['😀', false],
|
||||
['(#@()*&%()@*#&09827340982374}{:">?></\'\\~`', false],
|
||||
|
||||
['Foo', true],
|
||||
['FOO', true],
|
||||
['FöÖ', true],
|
||||
['FöÖ', true],
|
||||
['\\Foo', true],
|
||||
].forEach(([str, result]) => {
|
||||
assert.equal(strings.containsUppercaseCharacter(<string>str), result, `Wrong result for ${str}`);
|
||||
});
|
||||
});
|
||||
|
||||
test('containsUppercaseCharacter (ignoreEscapedChars)', () => {
|
||||
[
|
||||
['\\Woo', false],
|
||||
['f\\S\\S', false],
|
||||
['foo', false],
|
||||
|
||||
['Foo', true],
|
||||
].forEach(([str, result]) => {
|
||||
assert.equal(strings.containsUppercaseCharacter(<string>str, true), result, `Wrong result for ${str}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -86,5 +86,5 @@ export function onError(error: Error, done: () => void): void {
|
||||
}
|
||||
|
||||
export function toResource(this: any, path: string) {
|
||||
return URI.file(paths.join('C:\\', new Buffer(this.test.fullTitle()).toString('base64'), path));
|
||||
return URI.file(paths.join('C:\\', Buffer.from(this.test.fullTitle()).toString('base64'), path));
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user