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:
Karl Burtram
2018-04-04 15:27:51 -07:00
committed by GitHub
parent 5fba3e31b4
commit dafb780987
9412 changed files with 141255 additions and 98813 deletions
+35
View File
@@ -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;
}
+25 -1
View File
@@ -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
View File
@@ -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]);
}
}
+6 -1
View File
@@ -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();
+50 -34
View File
@@ -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);
});
}
}
+1 -1
View File
@@ -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);
-7
View File
@@ -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 */
}
+83 -9
View File
@@ -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);
}
}
+5 -5
View File
@@ -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;
}
+1 -1
View File
@@ -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;
}
+89 -19
View File
@@ -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;
}
+47 -26
View File
@@ -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 */
+6 -6
View File
@@ -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;
}
+1
View File
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
.monaco-list {
position: relative;
height: 100%;
width: 100%;
white-space: nowrap;
+6
View File
@@ -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[];
+13 -5
View File
@@ -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);
}
+53 -14
View File
@@ -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() {
+201 -60
View File
@@ -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) {
+1 -1
View File
@@ -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));
}
}
+13 -5
View File
@@ -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;
+61 -78
View File
@@ -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);
}
}
+149
View File
@@ -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;
+2 -83
View File
@@ -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();
}
}
+2 -1
View File
@@ -233,6 +233,7 @@ export class ActionRunner implements IActionRunner {
}
public dispose(): void {
// noop
this._onDidBeforeRun.dispose();
this._onDidRun.dispose();
}
}
+17
View File
@@ -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
View File
@@ -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;
}
}
}
+16
View File
@@ -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);
}
+36 -14
View File
@@ -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;
}
}
}
+1 -1
View File
@@ -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();
-325
View File
@@ -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);
}
}
}
}
+11 -11
View File
@@ -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;
+17 -1
View File
@@ -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;
+6 -2
View File
@@ -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;
}
+4 -3
View File
@@ -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, '&&');
}
}
+4 -3
View File
@@ -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;
}
+1 -1
View File
@@ -3,6 +3,6 @@
[{
"name": "chjj-marked",
"repositoryURL": "https://github.com/npmcomponent/chjj-marked",
"version": "0.3.6",
"version": "0.3.12",
"license": "MIT"
}]
+199 -104
View File
@@ -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;
+1 -2
View File
@@ -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;
}
+2
View File
@@ -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 {
+128
View File
@@ -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;
}
+5 -1
View File
@@ -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;
+44 -4
View File
@@ -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
};
+18 -31
View File
@@ -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,
+29 -4
View File
@@ -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;
}
+12
View File
@@ -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;
}
+15 -9
View File
@@ -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;
}
}
}
+18 -11
View File
@@ -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);
};
}
})();
})();
+4 -7
View File
@@ -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;
+5 -9
View File
@@ -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()})`);
}
});
}
}
+15 -2
View File
@@ -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()})`;
}
+8 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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);
});
}
}
+9
View File
@@ -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.
+13 -16
View File
@@ -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
View File
@@ -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) => {
+15 -4
View File
@@ -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);
});
}
+5 -4
View File
@@ -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 {
+1 -2
View File
@@ -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);
}
}
+1 -1
View File
@@ -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);
};
+2 -2
View File
@@ -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
View File
@@ -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 };
}
+34 -6
View File
@@ -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
-48
View File
@@ -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));
}
}
}
+4 -3
View File
@@ -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();
}
}
}
-2
View File
@@ -42,8 +42,6 @@ function select(builder: Builder, selector: string, offdom?: boolean): MultiBuil
}
suite('Builder', () => {
test('Binding', function () {
});
});
+28
View File
@@ -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');
});
});
});
+12 -6
View File
@@ -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();
});
});
+37
View File
@@ -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', () => {
+4 -2
View File
@@ -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 () {
+2 -2
View File
@@ -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 () {
+63
View File
@@ -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 },
]);
});
});
+54
View File
@@ -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();
});
});
+33
View File
@@ -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}`);
});
});
});
+1 -1
View File
@@ -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