mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge from vscode 1fbacccbc900bb59ba8a8f26a4128d48a1c97842
This commit is contained in:
44
src/vs/base/browser/linkedText.ts
Normal file
44
src/vs/base/browser/linkedText.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export interface ILink {
|
||||
readonly label: string;
|
||||
readonly href: string;
|
||||
readonly title?: string;
|
||||
}
|
||||
|
||||
export type LinkedTextNode = string | ILink;
|
||||
export type LinkedText = LinkedTextNode[];
|
||||
|
||||
const LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:)[^\)\s]+)(?: "([^"]+)")?\)/gi;
|
||||
|
||||
export function parseLinkedText(text: string): LinkedText {
|
||||
const result: LinkedTextNode[] = [];
|
||||
|
||||
let index = 0;
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
while (match = LINK_REGEX.exec(text)) {
|
||||
if (match.index - index > 0) {
|
||||
result.push(text.substring(index, match.index));
|
||||
}
|
||||
|
||||
const [, label, href, title] = match;
|
||||
|
||||
if (title) {
|
||||
result.push({ label, href, title });
|
||||
} else {
|
||||
result.push({ label, href });
|
||||
}
|
||||
|
||||
index = match.index + match[0].length;
|
||||
}
|
||||
|
||||
if (index < text.length) {
|
||||
result.push(text.substring(index));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Gesture, EventType } from 'vs/base/browser/touch';
|
||||
|
||||
export interface IButtonOptions extends IButtonStyles {
|
||||
title?: boolean;
|
||||
title?: boolean | string;
|
||||
}
|
||||
|
||||
export interface IButtonStyles {
|
||||
@@ -151,10 +151,10 @@ export class Button extends Disposable {
|
||||
DOM.addClass(this._element, 'monaco-text-button');
|
||||
}
|
||||
this._element.textContent = value;
|
||||
//{{SQL CARBON EDIT}}
|
||||
this._element.setAttribute('aria-label', value);
|
||||
//{{END}}
|
||||
if (this.options.title) {
|
||||
this._element.setAttribute('aria-label', value); // {{SQL CARBON EDIT}}
|
||||
if (typeof this.options.title === 'string') {
|
||||
this._element.title = this.options.title;
|
||||
} else if (this.options.title) {
|
||||
this._element.title = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Event } from 'vs/base/common/event';
|
||||
import { IView, IViewSize } from 'vs/base/browser/ui/grid/grid';
|
||||
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { IBoundarySashes } from 'vs/base/browser/ui/grid/gridview';
|
||||
|
||||
export interface CenteredViewState {
|
||||
leftMarginRatio: number;
|
||||
@@ -72,6 +73,19 @@ export class CenteredViewLayout implements IDisposable {
|
||||
get maximumHeight(): number { return this.view.maximumHeight; }
|
||||
get onDidChange(): Event<IViewSize | undefined> { return this.view.onDidChange; }
|
||||
|
||||
private _boundarySashes: IBoundarySashes = {};
|
||||
get boundarySashes(): IBoundarySashes { return this._boundarySashes; }
|
||||
set boundarySashes(boundarySashes: IBoundarySashes) {
|
||||
this._boundarySashes = boundarySashes;
|
||||
|
||||
if (!this.splitView) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.splitView.orthogonalStartSash = boundarySashes.top;
|
||||
this.splitView.orthogonalEndSash = boundarySashes.bottom;
|
||||
}
|
||||
|
||||
layout(width: number, height: number): void {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
@@ -119,6 +133,8 @@ export class CenteredViewLayout implements IDisposable {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
styles: this.style
|
||||
});
|
||||
this.splitView.orthogonalStartSash = this.boundarySashes.top;
|
||||
this.splitView.orthogonalEndSash = this.boundarySashes.bottom;
|
||||
|
||||
this.splitViewDisposables.add(this.splitView.onDidSashChange(() => {
|
||||
if (this.splitView) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
@font-face {
|
||||
font-family: "codicon";
|
||||
src: url("./codicon.ttf?be537a78617db0869caa4b4cc683a24a") format("truetype");
|
||||
src: url("./codicon.ttf?6caeeccc06315e827f3bff83885456fb") format("truetype");
|
||||
}
|
||||
|
||||
.codicon[class*='codicon-'] {
|
||||
@@ -413,4 +413,5 @@
|
||||
.codicon-feedback:before { content: "\eb96" }
|
||||
.codicon-group-by-ref-type:before { content: "\eb97" }
|
||||
.codicon-ungroup-by-ref-type:before { content: "\eb98" }
|
||||
.codicon-debug-alt:before { content: "\f101" }
|
||||
.codicon-debug-alt-2:before { content: "\f101" }
|
||||
.codicon-debug-alt:before { content: "\f102" }
|
||||
|
||||
Binary file not shown.
@@ -7,7 +7,7 @@ import 'vs/css!./gridview';
|
||||
import { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { tail2 as tail, equals } from 'vs/base/common/arrays';
|
||||
import { orthogonal, IView as IGridViewView, GridView, Sizing as GridViewSizing, Box, IGridViewStyles, IViewSize, IGridViewOptions } from './gridview';
|
||||
import { orthogonal, IView as IGridViewView, GridView, Sizing as GridViewSizing, Box, IGridViewStyles, IViewSize, IGridViewOptions, IBoundarySashes } from './gridview';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export { Orientation, Sizing as GridViewSizing, IViewSize, orthogonal, LayoutPriority } from './gridview';
|
||||
@@ -212,6 +212,9 @@ export class Grid<T extends IView = IView> extends Disposable {
|
||||
get maximumHeight(): number { return this.gridview.maximumHeight; }
|
||||
get onDidChange(): Event<{ width: number; height: number; } | undefined> { return this.gridview.onDidChange; }
|
||||
|
||||
get boundarySashes(): IBoundarySashes { return this.gridview.boundarySashes; }
|
||||
set boundarySashes(boundarySashes: IBoundarySashes) { this.gridview.boundarySashes = boundarySashes; }
|
||||
|
||||
get element(): HTMLElement { return this.gridview.element; }
|
||||
|
||||
private didLayout = false;
|
||||
|
||||
@@ -21,6 +21,20 @@ export interface IViewSize {
|
||||
readonly height: number;
|
||||
}
|
||||
|
||||
interface IRelativeBoundarySashes {
|
||||
readonly start?: Sash;
|
||||
readonly end?: Sash;
|
||||
readonly orthogonalStart?: Sash;
|
||||
readonly orthogonalEnd?: Sash;
|
||||
}
|
||||
|
||||
export interface IBoundarySashes {
|
||||
readonly top?: Sash;
|
||||
readonly right?: Sash;
|
||||
readonly bottom?: Sash;
|
||||
readonly left?: Sash;
|
||||
}
|
||||
|
||||
export interface IView {
|
||||
readonly element: HTMLElement;
|
||||
readonly minimumWidth: number;
|
||||
@@ -32,6 +46,7 @@ export interface IView {
|
||||
readonly snap?: boolean;
|
||||
layout(width: number, height: number, top: number, left: number): void;
|
||||
setVisible?(visible: boolean): void;
|
||||
setBoundarySashes?(sashes: IBoundarySashes): void;
|
||||
}
|
||||
|
||||
export interface ISerializableView extends IView {
|
||||
@@ -125,6 +140,22 @@ interface ILayoutContext {
|
||||
readonly absoluteOrthogonalSize: number;
|
||||
}
|
||||
|
||||
function toAbsoluteBoundarySashes(sashes: IRelativeBoundarySashes, orientation: Orientation): IBoundarySashes {
|
||||
if (orientation === Orientation.HORIZONTAL) {
|
||||
return { left: sashes.start, right: sashes.end, top: sashes.orthogonalStart, bottom: sashes.orthogonalEnd };
|
||||
} else {
|
||||
return { top: sashes.start, bottom: sashes.end, left: sashes.orthogonalStart, right: sashes.orthogonalEnd };
|
||||
}
|
||||
}
|
||||
|
||||
function fromAbsoluteBoundarySashes(sashes: IBoundarySashes, orientation: Orientation): IRelativeBoundarySashes {
|
||||
if (orientation === Orientation.HORIZONTAL) {
|
||||
return { start: sashes.left, end: sashes.right, orthogonalStart: sashes.top, orthogonalEnd: sashes.bottom };
|
||||
} else {
|
||||
return { start: sashes.top, end: sashes.bottom, orthogonalStart: sashes.left, orthogonalEnd: sashes.right };
|
||||
}
|
||||
}
|
||||
|
||||
class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
|
||||
readonly element: HTMLElement;
|
||||
@@ -217,10 +248,27 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
private splitviewSashResetDisposable: IDisposable = Disposable.None;
|
||||
private childrenSashResetDisposable: IDisposable = Disposable.None;
|
||||
|
||||
get orthogonalStartSash(): Sash | undefined { return this.splitview.orthogonalStartSash; }
|
||||
set orthogonalStartSash(sash: Sash | undefined) { this.splitview.orthogonalStartSash = sash; }
|
||||
get orthogonalEndSash(): Sash | undefined { return this.splitview.orthogonalEndSash; }
|
||||
set orthogonalEndSash(sash: Sash | undefined) { this.splitview.orthogonalEndSash = sash; }
|
||||
private _boundarySashes: IRelativeBoundarySashes = {};
|
||||
get boundarySashes(): IRelativeBoundarySashes { return this._boundarySashes; }
|
||||
set boundarySashes(boundarySashes: IRelativeBoundarySashes) {
|
||||
this._boundarySashes = boundarySashes;
|
||||
|
||||
this.splitview.orthogonalStartSash = boundarySashes.orthogonalStart;
|
||||
this.splitview.orthogonalEndSash = boundarySashes.orthogonalEnd;
|
||||
|
||||
for (let index = 0; index < this.children.length; index++) {
|
||||
const child = this.children[index];
|
||||
const first = index === 0;
|
||||
const last = index === this.children.length - 1;
|
||||
|
||||
child.boundarySashes = {
|
||||
start: boundarySashes.orthogonalStart,
|
||||
end: boundarySashes.orthogonalEnd,
|
||||
orthogonalStart: first ? boundarySashes.start : child.boundarySashes.orthogonalStart,
|
||||
orthogonalEnd: last ? boundarySashes.end : child.boundarySashes.orthogonalEnd,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
readonly orientation: Orientation,
|
||||
@@ -260,9 +308,15 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
this.splitview = new SplitView(this.element, { ...options, descriptor });
|
||||
|
||||
this.children.forEach((node, index) => {
|
||||
// Set up orthogonal sashes for children
|
||||
node.orthogonalStartSash = this.splitview.sashes[index - 1];
|
||||
node.orthogonalEndSash = this.splitview.sashes[index];
|
||||
const first = index === 0;
|
||||
const last = index === this.children.length;
|
||||
|
||||
node.boundarySashes = {
|
||||
start: this.boundarySashes.orthogonalStart,
|
||||
end: this.boundarySashes.orthogonalEnd,
|
||||
orthogonalStart: first ? this.boundarySashes.start : this.splitview.sashes[index - 1],
|
||||
orthogonalEnd: last ? this.boundarySashes.end : this.splitview.sashes[index],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -335,15 +389,26 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
const first = index === 0;
|
||||
const last = index === this.children.length;
|
||||
this.children.splice(index, 0, node);
|
||||
node.orthogonalStartSash = this.splitview.sashes[index - 1];
|
||||
node.orthogonalEndSash = this.splitview.sashes[index];
|
||||
|
||||
node.boundarySashes = {
|
||||
start: this.boundarySashes.orthogonalStart,
|
||||
end: this.boundarySashes.orthogonalEnd,
|
||||
orthogonalStart: first ? this.boundarySashes.start : this.splitview.sashes[index - 1],
|
||||
orthogonalEnd: last ? this.boundarySashes.end : this.splitview.sashes[index],
|
||||
};
|
||||
|
||||
if (!first) {
|
||||
this.children[index - 1].orthogonalEndSash = this.splitview.sashes[index - 1];
|
||||
this.children[index - 1].boundarySashes = {
|
||||
...this.children[index - 1].boundarySashes,
|
||||
orthogonalEnd: this.splitview.sashes[index - 1]
|
||||
};
|
||||
}
|
||||
|
||||
if (!last) {
|
||||
this.children[index + 1].orthogonalStartSash = this.splitview.sashes[index];
|
||||
this.children[index + 1].boundarySashes = {
|
||||
...this.children[index + 1].boundarySashes,
|
||||
orthogonalStart: this.splitview.sashes[index]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,11 +428,17 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
const [child] = this.children.splice(index, 1);
|
||||
|
||||
if (!first) {
|
||||
this.children[index - 1].orthogonalEndSash = this.splitview.sashes[index - 1];
|
||||
this.children[index - 1].boundarySashes = {
|
||||
...this.children[index - 1].boundarySashes,
|
||||
orthogonalEnd: this.splitview.sashes[index - 1]
|
||||
};
|
||||
}
|
||||
|
||||
if (!last) { // [0,1,2,3] (2) => [0,1,3]
|
||||
this.children[index].orthogonalStartSash = this.splitview.sashes[Math.max(index - 1, 0)];
|
||||
this.children[index].boundarySashes = {
|
||||
...this.children[index].boundarySashes,
|
||||
orthogonalStart: this.splitview.sashes[Math.max(index - 1, 0)]
|
||||
};
|
||||
}
|
||||
|
||||
return child;
|
||||
@@ -408,7 +479,12 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
to = clamp(to, 0, this.children.length);
|
||||
|
||||
this.splitview.swapViews(from, to);
|
||||
[this.children[from].orthogonalStartSash, this.children[from].orthogonalEndSash, this.children[to].orthogonalStartSash, this.children[to].orthogonalEndSash] = [this.children[to].orthogonalStartSash, this.children[to].orthogonalEndSash, this.children[from].orthogonalStartSash, this.children[from].orthogonalEndSash];
|
||||
|
||||
// swap boundary sashes
|
||||
[this.children[from].boundarySashes, this.children[to].boundarySashes]
|
||||
= [this.children[from].boundarySashes, this.children[to].boundarySashes];
|
||||
|
||||
// swap children
|
||||
[this.children[from], this.children[to]] = [this.children[to], this.children[from]];
|
||||
|
||||
this.onDidChildrenChange();
|
||||
@@ -655,12 +731,14 @@ class LeafNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
return this.orientation === Orientation.HORIZONTAL ? this.maximumWidth : this.maximumHeight;
|
||||
}
|
||||
|
||||
set orthogonalStartSash(sash: Sash) {
|
||||
// noop
|
||||
}
|
||||
private _boundarySashes: IRelativeBoundarySashes = {};
|
||||
get boundarySashes(): IRelativeBoundarySashes { return this._boundarySashes; }
|
||||
set boundarySashes(boundarySashes: IRelativeBoundarySashes) {
|
||||
this._boundarySashes = boundarySashes;
|
||||
|
||||
set orthogonalEndSash(sash: Sash) {
|
||||
// noop
|
||||
if (this.view.setBoundarySashes) {
|
||||
this.view.setBoundarySashes(toAbsoluteBoundarySashes(boundarySashes, this.orientation));
|
||||
}
|
||||
}
|
||||
|
||||
layout(size: number, offset: number, ctx: ILayoutContext | undefined): void {
|
||||
@@ -764,6 +842,7 @@ export class GridView implements IDisposable {
|
||||
const { size, orthogonalSize } = this._root;
|
||||
this.root = flipNode(this._root, orthogonalSize, size);
|
||||
this.root.layout(size, 0, { orthogonalSize, absoluteOffset: 0, absoluteOrthogonalOffset: 0, absoluteSize: size, absoluteOrthogonalSize: orthogonalSize });
|
||||
this.boundarySashes = this.boundarySashes;
|
||||
}
|
||||
|
||||
get width(): number { return this.root.width; }
|
||||
@@ -777,6 +856,13 @@ export class GridView implements IDisposable {
|
||||
private _onDidChange = new Relay<IViewSize | undefined>();
|
||||
readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
private _boundarySashes: IBoundarySashes = {};
|
||||
get boundarySashes(): IBoundarySashes { return this._boundarySashes; }
|
||||
set boundarySashes(boundarySashes: IBoundarySashes) {
|
||||
this._boundarySashes = boundarySashes;
|
||||
this.root.boundarySashes = fromAbsoluteBoundarySashes(boundarySashes, this.orientation);
|
||||
}
|
||||
|
||||
/**
|
||||
* The first layout controller makes sure layout only propagates
|
||||
* to the views after the very first call to gridview.layout()
|
||||
@@ -898,6 +984,7 @@ export class GridView implements IDisposable {
|
||||
// we must promote sibling to be the new root
|
||||
parent.removeChild(0);
|
||||
this.root = sibling;
|
||||
this.boundarySashes = this.boundarySashes;
|
||||
return node.view;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,14 +128,14 @@ class AsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements IT
|
||||
}
|
||||
}
|
||||
|
||||
function asTreeEvent<TInput, T>(e: ITreeEvent<IAsyncDataTreeNode<TInput, T>>): ITreeEvent<T> {
|
||||
function asTreeEvent<TInput, T>(e: ITreeEvent<IAsyncDataTreeNode<TInput, T> | null>): ITreeEvent<T> {
|
||||
return {
|
||||
browserEvent: e.browserEvent,
|
||||
elements: e.elements.map(e => e.element as T)
|
||||
elements: e.elements.map(e => e!.element as T)
|
||||
};
|
||||
}
|
||||
|
||||
function asTreeMouseEvent<TInput, T>(e: ITreeMouseEvent<IAsyncDataTreeNode<TInput, T>>): ITreeMouseEvent<T> {
|
||||
function asTreeMouseEvent<TInput, T>(e: ITreeMouseEvent<IAsyncDataTreeNode<TInput, T> | null>): ITreeMouseEvent<T> {
|
||||
return {
|
||||
browserEvent: e.browserEvent,
|
||||
element: e.element && e.element.element as T,
|
||||
@@ -143,7 +143,7 @@ function asTreeMouseEvent<TInput, T>(e: ITreeMouseEvent<IAsyncDataTreeNode<TInpu
|
||||
};
|
||||
}
|
||||
|
||||
function asTreeContextMenuEvent<TInput, T>(e: ITreeContextMenuEvent<IAsyncDataTreeNode<TInput, T>>): ITreeContextMenuEvent<T> {
|
||||
function asTreeContextMenuEvent<TInput, T>(e: ITreeContextMenuEvent<IAsyncDataTreeNode<TInput, T> | null>): ITreeContextMenuEvent<T> {
|
||||
return {
|
||||
browserEvent: e.browserEvent,
|
||||
element: e.element && e.element.element as T,
|
||||
@@ -793,7 +793,11 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
return result.finally(() => this.refreshPromises.delete(node));
|
||||
}
|
||||
|
||||
private _onDidChangeCollapseState({ node, deep }: ICollapseStateChangeEvent<IAsyncDataTreeNode<TInput, T>, any>): void {
|
||||
private _onDidChangeCollapseState({ node, deep }: ICollapseStateChangeEvent<IAsyncDataTreeNode<TInput, T> | null, any>): void {
|
||||
if (node.element === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node.collapsed && node.element.stale) {
|
||||
if (deep) {
|
||||
this.collapse(node.element.element as T);
|
||||
|
||||
@@ -37,7 +37,7 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
|
||||
private dataSource: IDataSource<TInput, T>,
|
||||
options: IDataTreeOptions<T, TFilterData> = {}
|
||||
) {
|
||||
super(user, container, delegate, renderers, options);
|
||||
super(user, container, delegate, renderers, options as IDataTreeOptions<T | null, TFilterData>);
|
||||
this.identityProvider = options.identityProvider;
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
|
||||
throw new TreeError(this.user, 'Can\'t get tree view state without an identity provider');
|
||||
}
|
||||
|
||||
const getId = (element: T) => this.identityProvider!.getId(element).toString();
|
||||
const getId = (element: T | null) => this.identityProvider!.getId(element!).toString();
|
||||
const focus = this.getFocus().map(getId);
|
||||
const selection = this.getSelection().map(getId);
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
|
||||
renderers: ITreeRenderer<T, TFilterData, any>[],
|
||||
options: IObjectTreeOptions<T, TFilterData> = {}
|
||||
) {
|
||||
super(user, container, delegate, renderers, options);
|
||||
super(user, container, delegate, renderers, options as IObjectTreeOptions<T | null, TFilterData>);
|
||||
}
|
||||
|
||||
setChildren(element: T | null, children?: ISequence<ITreeElement<T>>): void {
|
||||
@@ -181,7 +181,7 @@ export class CompressibleObjectTree<T extends NonNullable<any>, TFilterData = vo
|
||||
) {
|
||||
const compressedTreeNodeProvider = () => this;
|
||||
const compressibleRenderers = renderers.map(r => new CompressibleRenderer<T, TFilterData, any>(compressedTreeNodeProvider, r));
|
||||
super(user, container, delegate, compressibleRenderers, asObjectTreeOptions(compressedTreeNodeProvider, options));
|
||||
super(user, container, delegate, compressibleRenderers, asObjectTreeOptions<T, TFilterData>(compressedTreeNodeProvider, options));
|
||||
}
|
||||
|
||||
setChildren(element: T | null, children?: ISequence<ICompressedTreeElement<T>>): void {
|
||||
|
||||
@@ -79,35 +79,47 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
const insertedElements = new Set<T | null>();
|
||||
const insertedElementIds = new Set<string>();
|
||||
|
||||
const _onDidCreateNode = (node: ITreeNode<T, TFilterData>) => {
|
||||
insertedElements.add(node.element);
|
||||
this.nodes.set(node.element, node);
|
||||
const _onDidCreateNode = (node: ITreeNode<T | null, TFilterData>) => {
|
||||
if (node.element === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tnode = node as ITreeNode<T, TFilterData>;
|
||||
|
||||
insertedElements.add(tnode.element);
|
||||
this.nodes.set(tnode.element, tnode);
|
||||
|
||||
if (this.identityProvider) {
|
||||
const id = this.identityProvider.getId(node.element).toString();
|
||||
const id = this.identityProvider.getId(tnode.element).toString();
|
||||
insertedElementIds.add(id);
|
||||
this.nodesByIdentity.set(id, node);
|
||||
this.nodesByIdentity.set(id, tnode);
|
||||
}
|
||||
|
||||
if (onDidCreateNode) {
|
||||
onDidCreateNode(node);
|
||||
onDidCreateNode(tnode);
|
||||
}
|
||||
};
|
||||
|
||||
const _onDidDeleteNode = (node: ITreeNode<T, TFilterData>) => {
|
||||
if (!insertedElements.has(node.element)) {
|
||||
this.nodes.delete(node.element);
|
||||
const _onDidDeleteNode = (node: ITreeNode<T | null, TFilterData>) => {
|
||||
if (node.element === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tnode = node as ITreeNode<T, TFilterData>;
|
||||
|
||||
if (!insertedElements.has(tnode.element)) {
|
||||
this.nodes.delete(tnode.element);
|
||||
}
|
||||
|
||||
if (this.identityProvider) {
|
||||
const id = this.identityProvider.getId(node.element).toString();
|
||||
const id = this.identityProvider.getId(tnode.element).toString();
|
||||
if (!insertedElementIds.has(id)) {
|
||||
this.nodesByIdentity.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (onDidDeleteNode) {
|
||||
onDidDeleteNode(node);
|
||||
onDidDeleteNode(tnode);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -7,18 +7,27 @@ import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface CancellationToken {
|
||||
readonly isCancellationRequested: boolean;
|
||||
|
||||
/**
|
||||
* An event emitted when cancellation is requested
|
||||
* A flag signalling is cancellation has been requested.
|
||||
*/
|
||||
readonly isCancellationRequested: boolean;
|
||||
|
||||
/**
|
||||
* An event which fires when cancellation is requested. This event
|
||||
* only ever fires `once` as cancellation can only happen once. Listeners
|
||||
* that are registered after cancellation will be called (next event loop run),
|
||||
* but also only once.
|
||||
*
|
||||
* @event
|
||||
*/
|
||||
readonly onCancellationRequested: Event<any>;
|
||||
readonly onCancellationRequested: (listener: (e: any) => any, thisArgs?: any, disposables?: IDisposable[]) => IDisposable;
|
||||
}
|
||||
|
||||
const shortcutEvent = Object.freeze(function (callback, context?): IDisposable {
|
||||
const shortcutEvent: Event<any> = Object.freeze(function (callback, context?): IDisposable {
|
||||
const handle = setTimeout(callback.bind(context), 0);
|
||||
return { dispose() { clearTimeout(handle); } };
|
||||
} as Event<any>);
|
||||
});
|
||||
|
||||
export namespace CancellationToken {
|
||||
|
||||
|
||||
@@ -85,6 +85,8 @@ export namespace Event {
|
||||
* Given a collection of events, returns a single event which emits
|
||||
* whenever any of the provided events emit.
|
||||
*/
|
||||
export function any<T>(...events: Event<T>[]): Event<T>;
|
||||
export function any(...events: Event<any>[]): Event<void>;
|
||||
export function any<T>(...events: Event<T>[]): Event<T> {
|
||||
return (listener, thisArgs = null, disposables?) => combinedDisposable(...events.map(event => event(e => listener.call(thisArgs, e), null, disposables)));
|
||||
}
|
||||
@@ -271,6 +273,7 @@ export namespace Event {
|
||||
map<O>(fn: (i: T) => O): IChainableEvent<O>;
|
||||
forEach(fn: (i: T) => void): IChainableEvent<T>;
|
||||
filter(fn: (e: T) => boolean): IChainableEvent<T>;
|
||||
filter<R>(fn: (e: T | R) => e is R): IChainableEvent<R>;
|
||||
reduce<R>(merge: (last: R | undefined, event: T) => R, initial?: R): IChainableEvent<R>;
|
||||
latch(): IChainableEvent<T>;
|
||||
debounce(merge: (last: T | undefined, event: T) => T, delay?: number, leading?: boolean, leakWarningThreshold?: number): IChainableEvent<T>;
|
||||
@@ -291,6 +294,8 @@ export namespace Event {
|
||||
return new ChainableEvent(forEach(this.event, fn));
|
||||
}
|
||||
|
||||
filter(fn: (e: T) => boolean): IChainableEvent<T>;
|
||||
filter<R>(fn: (e: T | R) => e is R): IChainableEvent<R>;
|
||||
filter(fn: (e: T) => boolean): IChainableEvent<T> {
|
||||
return new ChainableEvent(filter(this.event, fn));
|
||||
}
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { dirname } from 'vs/base/common/path';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import * as json from 'vs/base/common/json';
|
||||
import { statLink } from 'vs/base/node/pfs';
|
||||
import { realpath } from 'vs/base/node/extpath';
|
||||
import { watchFolder, watchFile } from 'vs/base/node/watcher';
|
||||
|
||||
export interface IConfigurationChangeEvent<T> {
|
||||
config: T;
|
||||
}
|
||||
|
||||
export interface IConfigWatcher<T> {
|
||||
path: string;
|
||||
hasParseErrors: boolean;
|
||||
|
||||
reload(callback: (config: T) => void): void;
|
||||
getConfig(): T;
|
||||
}
|
||||
|
||||
export interface IConfigOptions<T> {
|
||||
onError: (error: Error | string) => void;
|
||||
defaultConfig: T;
|
||||
changeBufferDelay?: number;
|
||||
parse?: (content: string, errors: any[]) => T;
|
||||
initCallback?: (config: T) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple helper to watch a configured file for changes and process its contents as JSON object.
|
||||
* Supports:
|
||||
* - comments in JSON files and errors
|
||||
* - symlinks for the config file itself
|
||||
* - delayed processing of changes to accomodate for lots of changes
|
||||
* - configurable defaults
|
||||
*/
|
||||
export class ConfigWatcher<T> extends Disposable implements IConfigWatcher<T> {
|
||||
private cache: T | undefined;
|
||||
private parseErrors: json.ParseError[] | undefined;
|
||||
private disposed: boolean | undefined;
|
||||
private loaded: boolean | undefined;
|
||||
private timeoutHandle: NodeJS.Timer | null | undefined;
|
||||
private readonly _onDidUpdateConfiguration: Emitter<IConfigurationChangeEvent<T>>;
|
||||
|
||||
constructor(private _path: string, private options: IConfigOptions<T> = { defaultConfig: Object.create(null), onError: error => console.error(error) }) {
|
||||
super();
|
||||
this._onDidUpdateConfiguration = this._register(new Emitter<IConfigurationChangeEvent<T>>());
|
||||
|
||||
this.registerWatcher();
|
||||
this.initAsync();
|
||||
}
|
||||
|
||||
get path(): string {
|
||||
return this._path;
|
||||
}
|
||||
|
||||
get hasParseErrors(): boolean {
|
||||
return !!this.parseErrors && this.parseErrors.length > 0;
|
||||
}
|
||||
|
||||
get onDidUpdateConfiguration(): Event<IConfigurationChangeEvent<T>> {
|
||||
return this._onDidUpdateConfiguration.event;
|
||||
}
|
||||
|
||||
private initAsync(): void {
|
||||
this.loadAsync(config => {
|
||||
if (!this.loaded) {
|
||||
this.updateCache(config); // prevent race condition if config was loaded sync already
|
||||
}
|
||||
if (this.options.initCallback) {
|
||||
this.options.initCallback(this.getConfig());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private updateCache(value: T): void {
|
||||
this.cache = value;
|
||||
this.loaded = true;
|
||||
}
|
||||
|
||||
private loadSync(): T {
|
||||
try {
|
||||
return this.parse(fs.readFileSync(this._path).toString());
|
||||
} catch (error) {
|
||||
return this.options.defaultConfig;
|
||||
}
|
||||
}
|
||||
|
||||
private loadAsync(callback: (config: T) => void): void {
|
||||
fs.readFile(this._path, (error, raw) => {
|
||||
if (error) {
|
||||
return callback(this.options.defaultConfig);
|
||||
}
|
||||
|
||||
return callback(this.parse(raw.toString()));
|
||||
});
|
||||
}
|
||||
|
||||
private parse(raw: string): T {
|
||||
let res: T;
|
||||
try {
|
||||
this.parseErrors = [];
|
||||
res = this.options.parse ? this.options.parse(raw, this.parseErrors) : json.parse(raw, this.parseErrors);
|
||||
|
||||
return res || this.options.defaultConfig;
|
||||
} catch (error) {
|
||||
return this.options.defaultConfig; // Ignore parsing errors
|
||||
}
|
||||
}
|
||||
|
||||
private registerWatcher(): void {
|
||||
|
||||
// Watch the parent of the path so that we detect ADD and DELETES
|
||||
const parentFolder = dirname(this._path);
|
||||
this.watch(parentFolder, true);
|
||||
|
||||
// Check if the path is a symlink and watch its target if so
|
||||
this.handleSymbolicLink().then(undefined, () => { /* ignore error */ });
|
||||
}
|
||||
|
||||
private async handleSymbolicLink(): Promise<void> {
|
||||
const { stat, symbolicLink } = await statLink(this._path);
|
||||
if (symbolicLink && !stat.isDirectory()) {
|
||||
const realPath = await realpath(this._path);
|
||||
|
||||
this.watch(realPath, false);
|
||||
}
|
||||
}
|
||||
|
||||
private watch(path: string, isFolder: boolean): void {
|
||||
if (this.disposed) {
|
||||
return; // avoid watchers that will never get disposed by checking for being disposed
|
||||
}
|
||||
|
||||
if (isFolder) {
|
||||
this._register(watchFolder(path, (type, path) => path === this._path ? this.onConfigFileChange() : undefined, error => this.options.onError(error)));
|
||||
} else {
|
||||
this._register(watchFile(path, () => this.onConfigFileChange(), error => this.options.onError(error)));
|
||||
}
|
||||
}
|
||||
|
||||
private onConfigFileChange(): void {
|
||||
if (this.timeoutHandle) {
|
||||
global.clearTimeout(this.timeoutHandle);
|
||||
this.timeoutHandle = null;
|
||||
}
|
||||
|
||||
// we can get multiple change events for one change, so we buffer through a timeout
|
||||
this.timeoutHandle = global.setTimeout(() => this.reload(), this.options.changeBufferDelay || 0);
|
||||
}
|
||||
|
||||
reload(callback?: (config: T) => void): void {
|
||||
this.loadAsync(currentConfig => {
|
||||
if (!objects.equals(currentConfig, this.cache)) {
|
||||
this.updateCache(currentConfig);
|
||||
|
||||
this._onDidUpdateConfiguration.fire({ config: currentConfig });
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
return callback(currentConfig);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getConfig(): T {
|
||||
this.ensureLoaded();
|
||||
|
||||
return this.cache!;
|
||||
}
|
||||
|
||||
private ensureLoaded(): void {
|
||||
if (!this.loaded) {
|
||||
this.updateCache(this.loadSync());
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposed = true;
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions
|
||||
private bufferedChunks: Buffer[] = [];
|
||||
private bytesBuffered = 0;
|
||||
|
||||
_write(chunk: Buffer, encoding: string, callback: (error: Error | null) => void): void {
|
||||
_write(chunk: Buffer, encoding: string, callback: (error: Error | null | undefined) => void): void {
|
||||
if (!Buffer.isBuffer(chunk)) {
|
||||
return callback(new Error('toDecodeStream(): data must be a buffer'));
|
||||
}
|
||||
@@ -84,7 +84,7 @@ export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions
|
||||
}
|
||||
}
|
||||
|
||||
_startDecodeStream(callback: (error: Error | null) => void): void {
|
||||
_startDecodeStream(callback: (error: Error | null | undefined) => void): void {
|
||||
|
||||
// detect encoding from buffer
|
||||
this.decodeStreamPromise = Promise.resolve(detectEncodingFromBuffer({
|
||||
|
||||
@@ -12,7 +12,6 @@ import { mkdirp, rimraf } from 'vs/base/node/pfs';
|
||||
import { open as _openZip, Entry, ZipFile } from 'yauzl';
|
||||
import * as yazl from 'yazl';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export interface IExtractOptions {
|
||||
overwrite?: boolean;
|
||||
@@ -80,7 +79,7 @@ function extractEntry(stream: Readable, fileName: string, mode: number, targetPa
|
||||
|
||||
let istream: WriteStream;
|
||||
|
||||
Event.once(token.onCancellationRequested)(() => {
|
||||
token.onCancellationRequested(() => {
|
||||
if (istream) {
|
||||
istream.destroy();
|
||||
}
|
||||
@@ -107,7 +106,7 @@ function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, tok
|
||||
let last = createCancelablePromise<void>(() => Promise.resolve());
|
||||
let extractedEntriesCount = 0;
|
||||
|
||||
Event.once(token.onCancellationRequested)(() => {
|
||||
token.onCancellationRequested(() => {
|
||||
last.cancel();
|
||||
zipfile.close();
|
||||
});
|
||||
|
||||
@@ -141,8 +141,8 @@ export class ItemRegistry {
|
||||
readonly onDidCollapseItem: Event<IItemCollapseEvent> = this._onDidCollapseItem.event;
|
||||
private _onDidAddTraitItem = new EventMultiplexer<IItemTraitEvent>();
|
||||
readonly onDidAddTraitItem: Event<IItemTraitEvent> = this._onDidAddTraitItem.event;
|
||||
private _onDidRemoveTraitItem = new EventMultiplexer<IItemCollapseEvent>();
|
||||
readonly onDidRemoveTraitItem: Event<IItemCollapseEvent> = this._onDidRemoveTraitItem.event;
|
||||
private _onDidRemoveTraitItem = new EventMultiplexer<IItemTraitEvent>();
|
||||
readonly onDidRemoveTraitItem: Event<IItemTraitEvent> = this._onDidRemoveTraitItem.event;
|
||||
private _onDidRefreshItem = new EventMultiplexer<Item>();
|
||||
readonly onDidRefreshItem: Event<Item> = this._onDidRefreshItem.event;
|
||||
private _onRefreshItemChildren = new EventMultiplexer<IItemChildrenRefreshEvent>();
|
||||
@@ -273,8 +273,8 @@ export class Item {
|
||||
readonly onDidCollapse: Event<IItemCollapseEvent> = this._onDidCollapse.event;
|
||||
private readonly _onDidAddTrait = new Emitter<IItemTraitEvent>();
|
||||
readonly onDidAddTrait: Event<IItemTraitEvent> = this._onDidAddTrait.event;
|
||||
private readonly _onDidRemoveTrait = new Emitter<IItemCollapseEvent>();
|
||||
readonly onDidRemoveTrait: Event<IItemCollapseEvent> = this._onDidRemoveTrait.event;
|
||||
private readonly _onDidRemoveTrait = new Emitter<IItemTraitEvent>();
|
||||
readonly onDidRemoveTrait: Event<IItemTraitEvent> = this._onDidRemoveTrait.event;
|
||||
private readonly _onDidRefresh = new Emitter<Item>();
|
||||
readonly onDidRefresh: Event<Item> = this._onDidRefresh.event;
|
||||
private readonly _onRefreshChildren = new Emitter<IItemChildrenRefreshEvent>();
|
||||
@@ -895,8 +895,8 @@ export class TreeModel {
|
||||
readonly onDidCollapseItem: Event<IItemCollapseEvent> = this._onDidCollapseItem.event;
|
||||
private _onDidAddTraitItem = new Relay<IItemTraitEvent>();
|
||||
readonly onDidAddTraitItem: Event<IItemTraitEvent> = this._onDidAddTraitItem.event;
|
||||
private _onDidRemoveTraitItem = new Relay<IItemCollapseEvent>();
|
||||
readonly onDidRemoveTraitItem: Event<IItemCollapseEvent> = this._onDidRemoveTraitItem.event;
|
||||
private _onDidRemoveTraitItem = new Relay<IItemTraitEvent>();
|
||||
readonly onDidRemoveTraitItem: Event<IItemTraitEvent> = this._onDidRemoveTraitItem.event;
|
||||
private _onDidRefreshItem = new Relay<Item>();
|
||||
readonly onDidRefreshItem: Event<Item> = this._onDidRefreshItem.event;
|
||||
private _onRefreshItemChildren = new Relay<IItemChildrenRefreshEvent>();
|
||||
|
||||
@@ -557,7 +557,7 @@ export class TreeView extends HeightMap {
|
||||
this.viewListeners.push(DOM.addDisposableListener(this.wrapper, 'MSGestureTap', (e) => this.onMsGestureTap(e)));
|
||||
|
||||
// these events come too fast, we throttle them
|
||||
this.viewListeners.push(DOM.addDisposableThrottledListener<IThrottledGestureEvent>(this.wrapper, 'MSGestureChange', (e) => this.onThrottledMsGestureChange(e), (lastEvent: IThrottledGestureEvent, event: MSGestureEvent): IThrottledGestureEvent => {
|
||||
this.viewListeners.push(DOM.addDisposableThrottledListener<IThrottledGestureEvent, MSGestureEvent>(this.wrapper, 'MSGestureChange', e => this.onThrottledMsGestureChange(e), (lastEvent, event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
|
||||
58
src/vs/base/test/browser/linkedText.test.ts
Normal file
58
src/vs/base/test/browser/linkedText.test.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { parseLinkedText } from 'vs/base/browser/linkedText';
|
||||
|
||||
suite('LinkedText', () => {
|
||||
test('parses correctly', () => {
|
||||
assert.deepEqual(parseLinkedText(''), []);
|
||||
assert.deepEqual(parseLinkedText('hello'), ['hello']);
|
||||
assert.deepEqual(parseLinkedText('hello there'), ['hello there']);
|
||||
assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href).'), [
|
||||
'Some message with ',
|
||||
{ label: 'link text', href: 'http://link.href' },
|
||||
'.'
|
||||
]);
|
||||
assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href "and a title").'), [
|
||||
'Some message with ',
|
||||
{ label: 'link text', href: 'http://link.href', title: 'and a title' },
|
||||
'.'
|
||||
]);
|
||||
assert.deepEqual(parseLinkedText('Some message with [link text](random stuff).'), [
|
||||
'Some message with [link text](random stuff).'
|
||||
]);
|
||||
assert.deepEqual(parseLinkedText('Some message with [https link](https://link.href).'), [
|
||||
'Some message with ',
|
||||
{ label: 'https link', href: 'https://link.href' },
|
||||
'.'
|
||||
]);
|
||||
assert.deepEqual(parseLinkedText('Some message with [https link](https:).'), [
|
||||
'Some message with [https link](https:).'
|
||||
]);
|
||||
assert.deepEqual(parseLinkedText('Some message with [a command](command:foobar).'), [
|
||||
'Some message with ',
|
||||
{ label: 'a command', href: 'command:foobar' },
|
||||
'.'
|
||||
]);
|
||||
assert.deepEqual(parseLinkedText('Some message with [a command](command:).'), [
|
||||
'Some message with [a command](command:).'
|
||||
]);
|
||||
assert.deepEqual(parseLinkedText('link [one](command:foo "nice") and link [two](http://foo)...'), [
|
||||
'link ',
|
||||
{ label: 'one', href: 'command:foo', title: 'nice' },
|
||||
' and link ',
|
||||
{ label: 'two', href: 'http://foo' },
|
||||
'...'
|
||||
]);
|
||||
assert.deepEqual(parseLinkedText('link\n[one](command:foo "nice")\nand link [two](http://foo)...'), [
|
||||
'link\n',
|
||||
{ label: 'one', href: 'command:foo', title: 'nice' },
|
||||
'\nand link ',
|
||||
{ label: 'two', href: 'http://foo' },
|
||||
'...'
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -108,6 +108,7 @@ suite('Resources', () => {
|
||||
assert.equal(joinPath(URI.file('/foo/bar'), '/./file.js').toString(), 'file:///foo/bar/file.js');
|
||||
assert.equal(joinPath(URI.file('/foo/bar'), '../file.js').toString(), 'file:///foo/file.js');
|
||||
}
|
||||
assert.equal(joinPath(URI.parse('foo://a/foo/bar')).toString(), 'foo://a/foo/bar');
|
||||
assert.equal(joinPath(URI.parse('foo://a/foo/bar'), '/file.js').toString(), 'foo://a/foo/bar/file.js');
|
||||
assert.equal(joinPath(URI.parse('foo://a/foo/bar'), 'file.js').toString(), 'foo://a/foo/bar/file.js');
|
||||
assert.equal(joinPath(URI.parse('foo://a/foo/bar/'), '/file.js').toString(), 'foo://a/foo/bar/file.js');
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as os from 'os';
|
||||
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as fs from 'fs';
|
||||
import * as uuid from 'vs/base/common/uuid';
|
||||
import { ConfigWatcher } from 'vs/base/node/config';
|
||||
import { testFile } from 'vs/base/test/node/utils';
|
||||
|
||||
suite('Config', () => {
|
||||
|
||||
test('defaults', () => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'config', id);
|
||||
const testFile = path.join(newDir, 'config.json');
|
||||
|
||||
let watcher = new ConfigWatcher<{}>(testFile);
|
||||
|
||||
let config = watcher.getConfig();
|
||||
assert.ok(config);
|
||||
assert.equal(Object.keys(config), 0);
|
||||
|
||||
watcher.dispose();
|
||||
|
||||
let watcher2 = new ConfigWatcher<any[]>(testFile, { defaultConfig: ['foo'], onError: console.error });
|
||||
|
||||
let config2 = watcher2.getConfig();
|
||||
assert.ok(Array.isArray(config2));
|
||||
assert.equal(config2.length, 1);
|
||||
|
||||
watcher.dispose();
|
||||
});
|
||||
|
||||
test('getConfig / getValue', function () {
|
||||
return testFile('config', 'config.json').then(res => {
|
||||
fs.writeFileSync(res.testFile, '// my comment\n{ "foo": "bar" }');
|
||||
|
||||
let watcher = new ConfigWatcher<{ foo: string; }>(res.testFile);
|
||||
|
||||
let config = watcher.getConfig();
|
||||
assert.ok(config);
|
||||
assert.equal(config.foo, 'bar');
|
||||
assert.ok(!watcher.hasParseErrors);
|
||||
|
||||
watcher.dispose();
|
||||
|
||||
return res.cleanUp();
|
||||
});
|
||||
});
|
||||
|
||||
test('getConfig / getValue - broken JSON', function () {
|
||||
return testFile('config', 'config.json').then(res => {
|
||||
fs.writeFileSync(res.testFile, '// my comment\n "foo": "bar ... ');
|
||||
|
||||
let watcher = new ConfigWatcher<{ foo: string; }>(res.testFile);
|
||||
|
||||
let config = watcher.getConfig();
|
||||
assert.ok(config);
|
||||
assert.ok(!config.foo);
|
||||
|
||||
assert.ok(watcher.hasParseErrors);
|
||||
|
||||
watcher.dispose();
|
||||
|
||||
return res.cleanUp();
|
||||
});
|
||||
});
|
||||
|
||||
// test('watching', function (done) {
|
||||
// this.timeout(10000); // watching is timing intense
|
||||
|
||||
// testFile('config', 'config.json').then(res => {
|
||||
// fs.writeFileSync(res.testFile, '// my comment\n{ "foo": "bar" }');
|
||||
|
||||
// let watcher = new ConfigWatcher<{ foo: string; }>(res.testFile);
|
||||
// watcher.getConfig(); // ensure we are in sync
|
||||
|
||||
// fs.writeFileSync(res.testFile, '// my comment\n{ "foo": "changed" }');
|
||||
|
||||
// watcher.onDidUpdateConfiguration(event => {
|
||||
// assert.ok(event);
|
||||
// assert.equal(event.config.foo, 'changed');
|
||||
// assert.equal(watcher.getValue('foo'), 'changed');
|
||||
|
||||
// watcher.dispose();
|
||||
|
||||
// res.cleanUp().then(done, done);
|
||||
// });
|
||||
// }, done);
|
||||
// });
|
||||
|
||||
// test('watching also works when file created later', function (done) {
|
||||
// this.timeout(10000); // watching is timing intense
|
||||
|
||||
// testFile('config', 'config.json').then(res => {
|
||||
// let watcher = new ConfigWatcher<{ foo: string; }>(res.testFile);
|
||||
// watcher.getConfig(); // ensure we are in sync
|
||||
|
||||
// fs.writeFileSync(res.testFile, '// my comment\n{ "foo": "changed" }');
|
||||
|
||||
// watcher.onDidUpdateConfiguration(event => {
|
||||
// assert.ok(event);
|
||||
// assert.equal(event.config.foo, 'changed');
|
||||
// assert.equal(watcher.getValue('foo'), 'changed');
|
||||
|
||||
// watcher.dispose();
|
||||
|
||||
// res.cleanUp().then(done, done);
|
||||
// });
|
||||
// }, done);
|
||||
// });
|
||||
|
||||
// test('watching detects the config file getting deleted', function (done) {
|
||||
// this.timeout(10000); // watching is timing intense
|
||||
|
||||
// testFile('config', 'config.json').then(res => {
|
||||
// fs.writeFileSync(res.testFile, '// my comment\n{ "foo": "bar" }');
|
||||
|
||||
// let watcher = new ConfigWatcher<{ foo: string; }>(res.testFile);
|
||||
// watcher.getConfig(); // ensure we are in sync
|
||||
|
||||
// watcher.onDidUpdateConfiguration(event => {
|
||||
// assert.ok(event);
|
||||
|
||||
// watcher.dispose();
|
||||
|
||||
// res.cleanUp().then(done, done);
|
||||
// });
|
||||
|
||||
// fs.unlinkSync(res.testFile);
|
||||
// }, done);
|
||||
// });
|
||||
|
||||
test('reload', function (done) {
|
||||
testFile('config', 'config.json').then(res => {
|
||||
fs.writeFileSync(res.testFile, '// my comment\n{ "foo": "bar" }');
|
||||
|
||||
let watcher = new ConfigWatcher<{ foo: string; }>(res.testFile, { changeBufferDelay: 100, onError: console.error, defaultConfig: { foo: 'bar' } });
|
||||
watcher.getConfig(); // ensure we are in sync
|
||||
|
||||
fs.writeFileSync(res.testFile, '// my comment\n{ "foo": "changed" }');
|
||||
|
||||
// still old values because change is not bubbling yet
|
||||
assert.equal(watcher.getConfig().foo, 'bar');
|
||||
|
||||
// force a load from disk
|
||||
watcher.reload(config => {
|
||||
assert.equal(config.foo, 'changed');
|
||||
assert.equal(watcher.getConfig().foo, 'changed');
|
||||
|
||||
watcher.dispose();
|
||||
|
||||
res.cleanUp().then(done, done);
|
||||
});
|
||||
}, done);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user