Merge branch 'master' into spdlog

This commit is contained in:
Joao Moreno
2017-12-04 11:52:11 +01:00
741 changed files with 9855 additions and 2879 deletions

View File

@@ -56,7 +56,9 @@ import { ExtHostFileSystem } from 'vs/workbench/api/node/extHostFileSystem';
import { FileChangeType, FileType } from 'vs/platform/files/common/files';
import { ExtHostDecorations } from 'vs/workbench/api/node/extHostDecorations';
import { toGlobPattern, toLanguageSelector } from 'vs/workbench/api/node/extHostTypeConverters';
import { ExtensionActivatedByAPI } from 'vs/workbench/api/node/extHostExtensionActivator';
import { ILogService } from 'vs/platform/log/common/log';
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
export interface IExtensionApiFactory {
(extension: IExtensionDescription): typeof vscode;
@@ -125,8 +127,16 @@ export function createApiFactory(
return function (extension: IExtensionDescription): typeof vscode {
if (extension.enableProposedApi && !extension.isBuiltin) {
const EXTENSION_ID = extension.id;
if (!isFalsyOrEmpty(product.extensionAllowedProposedApi)
&& product.extensionAllowedProposedApi.indexOf(extension.id) >= 0
) {
// fast lane -> proposed api is available to all extensions
// that are listed in product.json-files
extension.enableProposedApi = true;
} else if (extension.enableProposedApi && !extension.isBuiltin) {
if (
!initData.environment.enableProposedApiForAll &&
initData.environment.enableProposedApiFor.indexOf(extension.id) < 0
@@ -385,8 +395,13 @@ export function createApiFactory(
};
// namespace: workspace
let warnedRootPath = false;
const workspace: typeof vscode.workspace = {
get rootPath() {
if (!warnedRootPath) {
warnedRootPath = true;
extensionService.addMessage(EXTENSION_ID, Severity.Warning, 'workspace.rootPath is deprecated');
}
return extHostWorkspace.getPath();
},
set rootPath(value) {
@@ -500,8 +515,8 @@ export function createApiFactory(
get activeDebugConsole() {
return extHostDebugService.activeDebugConsole;
},
startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration) {
return extHostDebugService.startDebugging(folder, nameOrConfig);
get breakpoints() {
return extHostDebugService.breakpoints;
},
onDidStartDebugSession(listener, thisArg?, disposables?) {
return extHostDebugService.onDidStartDebugSession(listener, thisArg, disposables);
@@ -515,6 +530,12 @@ export function createApiFactory(
onDidReceiveDebugSessionCustomEvent(listener, thisArg?, disposables?) {
return extHostDebugService.onDidReceiveDebugSessionCustomEvent(listener, thisArg, disposables);
},
onDidChangeBreakpoints: proposedApiFunction(extension, (listener, thisArgs?, disposables?) => {
return extHostDebugService.onDidChangeBreakpoints(listener, thisArgs, disposables);
}),
startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration) {
return extHostDebugService.startDebugging(folder, nameOrConfig);
},
registerDebugConfigurationProvider(debugType: string, provider: vscode.DebugConfigurationProvider) {
return extHostDebugService.registerDebugConfigurationProvider(debugType, provider);
}
@@ -622,7 +643,7 @@ class Extension<T> implements vscode.Extension<T> {
}
activate(): Thenable<T> {
return this._extensionService.activateById(this.id, false).then(() => this.exports);
return this._extensionService.activateById(this.id, new ExtensionActivatedByAPI(false)).then(() => this.exports);
}
}

View File

@@ -239,11 +239,11 @@ export interface MainThreadEditorsShape extends IDisposable {
export interface MainThreadTreeViewsShape extends IDisposable {
$registerView(treeViewId: string): void;
$refresh(treeViewId: string, treeItemHandles: number[]): void;
$refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem }): void;
}
export interface MainThreadErrorsShape extends IDisposable {
$onUnexpectedError(err: any | SerializedError, extensionId: string | undefined): void;
$onUnexpectedError(err: any | SerializedError): void;
}
export interface MainThreadLanguageFeaturesShape extends IDisposable {
@@ -354,8 +354,10 @@ export interface MainThreadTaskShape extends IDisposable {
export interface MainThreadExtensionServiceShape extends IDisposable {
$localShowMessage(severity: Severity, msg: string): void;
$onExtensionActivated(extensionId: string, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number): void;
$onExtensionActivated(extensionId: string, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void;
$onExtensionActivationFailed(extensionId: string): void;
$onExtensionRuntimeError(extensionId: string, error: SerializedError): void;
$addMessage(extensionId: string, severity: Severity, message: string): void;
}
export interface SCMProviderFeatures {
@@ -418,6 +420,7 @@ export interface MainThreadDebugServiceShape extends IDisposable {
$startDebugging(folder: URI | undefined, nameOrConfig: string | vscode.DebugConfiguration): TPromise<boolean>;
$customDebugAdapterRequest(id: DebugSessionUUID, command: string, args: any): TPromise<any>;
$appendDebugConsole(value: string): TPromise<any>;
$startBreakpointEvents(): TPromise<any>;
}
export interface MainThreadWindowShape extends IDisposable {
@@ -492,7 +495,7 @@ export interface ExtHostDocumentsAndEditorsShape {
export interface ExtHostTreeViewsShape {
$getElements(treeViewId: string): TPromise<ITreeItem[]>;
$getChildren(treeViewId: string, treeItemHandle: number): TPromise<ITreeItem[]>;
$getChildren(treeViewId: string, treeItemHandle: string): TPromise<ITreeItem[]>;
}
export interface ExtHostWorkspaceShape {
@@ -622,6 +625,31 @@ export interface ExtHostTaskShape {
$provideTasks(handle: number): TPromise<TaskSet>;
}
export interface IBreakpointData {
type: 'source' | 'function';
id: string;
enabled: boolean;
condition?: string;
hitCondition?: string;
}
export interface ISourceBreakpointData extends IBreakpointData {
type: 'source';
sourceUriStr: string;
location: vscode.Position;
}
export interface IFunctionBreakpointData extends IBreakpointData {
type: 'function';
functionName: string;
}
export interface IBreakpointsDelta {
added?: (ISourceBreakpointData | IFunctionBreakpointData)[];
removed?: string[];
changed?: (ISourceBreakpointData | IFunctionBreakpointData)[];
}
export interface ExtHostDebugServiceShape {
$resolveDebugConfiguration(handle: number, folder: URI | undefined, debugConfiguration: any): TPromise<any>;
$provideDebugConfigurations(handle: number, folder: URI | undefined): TPromise<any[]>;
@@ -629,6 +657,7 @@ export interface ExtHostDebugServiceShape {
$acceptDebugSessionTerminated(id: DebugSessionUUID, type: string, name: string): void;
$acceptDebugSessionActiveChanged(id: DebugSessionUUID | undefined, type?: string, name?: string): void;
$acceptDebugSessionCustomEvent(id: DebugSessionUUID, type: string, name: string, event: any): void;
$acceptBreakpointsDelta(delat: IBreakpointsDelta): void;
}

View File

@@ -7,7 +7,7 @@
import { TPromise } from 'vs/base/common/winjs.base';
import Event, { Emitter } from 'vs/base/common/event';
import { asWinJsPromise } from 'vs/base/common/async';
import { MainContext, MainThreadDebugServiceShape, ExtHostDebugServiceShape, DebugSessionUUID, IMainContext } from 'vs/workbench/api/node/extHost.protocol';
import { MainContext, MainThreadDebugServiceShape, ExtHostDebugServiceShape, DebugSessionUUID, IMainContext, IBreakpointsDelta, ISourceBreakpointData, IFunctionBreakpointData } from 'vs/workbench/api/node/extHost.protocol';
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
import * as vscode from 'vscode';
@@ -43,6 +43,11 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
private _activeDebugConsole: ExtHostDebugConsole;
get activeDebugConsole(): ExtHostDebugConsole { return this._activeDebugConsole; }
private _breakpoints: Map<string, vscode.Breakpoint>;
private _breakpointEventsActive: boolean;
private _onDidChangeBreakpoints: Emitter<vscode.BreakpointsChangeEvent>;
constructor(mainContext: IMainContext, workspace: ExtHostWorkspace) {
@@ -58,7 +63,84 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
this._debugServiceProxy = mainContext.get(MainContext.MainThreadDebugService);
this._onDidChangeBreakpoints = new Emitter<vscode.BreakpointsChangeEvent>({
onFirstListenerAdd: () => {
this.startBreakpoints();
}
});
this._activeDebugConsole = new ExtHostDebugConsole(this._debugServiceProxy);
this._breakpoints = new Map<string, vscode.Breakpoint>();
this._breakpointEventsActive = false;
}
private startBreakpoints() {
if (!this._breakpointEventsActive) {
this._breakpointEventsActive = true;
this._debugServiceProxy.$startBreakpointEvents();
}
}
get onDidChangeBreakpoints(): Event<vscode.BreakpointsChangeEvent> {
return this._onDidChangeBreakpoints.event;
}
get breakpoints(): vscode.Breakpoint[] {
this.startBreakpoints();
const result: vscode.Breakpoint[] = [];
this._breakpoints.forEach(bp => result.push(bp));
return result;
}
public $acceptBreakpointsDelta(delta: IBreakpointsDelta): void {
let a: vscode.Breakpoint[] = [];
let r: vscode.Breakpoint[] = [];
let c: vscode.Breakpoint[] = [];
if (delta.added) {
a = delta.added.map(bpd => {
const id = bpd.id;
this._breakpoints.set(id, this.fromWire(bpd));
return bpd;
});
}
if (delta.removed) {
r = delta.removed.map(id => {
const bp = this._breakpoints.get(id);
if (bp) {
this._breakpoints.delete(id);
}
return bp;
});
}
if (delta.changed) {
c = delta.changed.map(bpd => {
const id = bpd.id;
this._breakpoints.set(id, this.fromWire(bpd));
return bpd;
});
}
this._onDidChangeBreakpoints.fire(Object.freeze({
added: Object.freeze<vscode.Breakpoint[]>(a || []),
removed: Object.freeze<vscode.Breakpoint[]>(r || []),
changed: Object.freeze<vscode.Breakpoint[]>(c || [])
}));
}
private fromWire(bp: ISourceBreakpointData | IFunctionBreakpointData): vscode.Breakpoint {
delete bp.id;
if (bp.type === 'source') {
(<any>bp).source = URI.parse(bp.sourceUriStr);
delete bp.sourceUriStr;
}
return bp;
}
public registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable {
@@ -87,7 +169,6 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
return asWinJsPromise(token => handler.provideDebugConfigurations(this.getFolder(folderUri), token));
}
public $resolveDebugConfiguration(handle: number, folderUri: URI | undefined, debugConfiguration: vscode.DebugConfiguration): TPromise<vscode.DebugConfiguration> {
let handler = this._handlers.get(handle);
if (!handler) {

View File

@@ -159,9 +159,24 @@ export class FailedExtension extends ActivatedExtension {
export interface IExtensionsActivatorHost {
showMessage(severity: Severity, message: string): void;
actualActivateExtension(extensionDescription: IExtensionDescription, startup: boolean): TPromise<ActivatedExtension>;
actualActivateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): TPromise<ActivatedExtension>;
}
export class ExtensionActivatedByEvent {
constructor(
public readonly startup: boolean,
public readonly activationEvent: string
) { }
}
export class ExtensionActivatedByAPI {
constructor(
public readonly startup: boolean
) { }
}
export type ExtensionActivationReason = ExtensionActivatedByEvent | ExtensionActivatedByAPI;
export class ExtensionsActivator {
private readonly _registry: ExtensionDescriptionRegistry;
@@ -192,23 +207,23 @@ export class ExtensionsActivator {
return this._activatedExtensions[extensionId];
}
public activateByEvent(activationEvent: string, startup: boolean): TPromise<void> {
public activateByEvent(activationEvent: string, reason: ExtensionActivationReason): TPromise<void> {
if (this._alreadyActivatedEvents[activationEvent]) {
return NO_OP_VOID_PROMISE;
}
let activateExtensions = this._registry.getExtensionDescriptionsForActivationEvent(activationEvent);
return this._activateExtensions(activateExtensions, startup, 0).then(() => {
return this._activateExtensions(activateExtensions, reason, 0).then(() => {
this._alreadyActivatedEvents[activationEvent] = true;
});
}
public activateById(extensionId: string, startup: boolean): TPromise<void> {
public activateById(extensionId: string, reason: ExtensionActivationReason): TPromise<void> {
let desc = this._registry.getExtensionDescription(extensionId);
if (!desc) {
throw new Error('Extension `' + extensionId + '` is not known');
}
return this._activateExtensions([desc], startup, 0);
return this._activateExtensions([desc], reason, 0);
}
/**
@@ -252,7 +267,7 @@ export class ExtensionsActivator {
}
}
private _activateExtensions(extensionDescriptions: IExtensionDescription[], startup: boolean, recursionLevel: number): TPromise<void> {
private _activateExtensions(extensionDescriptions: IExtensionDescription[], reason: ExtensionActivationReason, recursionLevel: number): TPromise<void> {
// console.log(recursionLevel, '_activateExtensions: ', extensionDescriptions.map(p => p.id));
if (extensionDescriptions.length === 0) {
return TPromise.as(void 0);
@@ -294,15 +309,15 @@ export class ExtensionsActivator {
if (red.length === 0) {
// Finally reached only leafs!
return TPromise.join(green.map((p) => this._activateExtension(p, startup))).then(_ => void 0);
return TPromise.join(green.map((p) => this._activateExtension(p, reason))).then(_ => void 0);
}
return this._activateExtensions(green, startup, recursionLevel + 1).then(_ => {
return this._activateExtensions(red, startup, recursionLevel + 1);
return this._activateExtensions(green, reason, recursionLevel + 1).then(_ => {
return this._activateExtensions(red, reason, recursionLevel + 1);
});
}
private _activateExtension(extensionDescription: IExtensionDescription, startup: boolean): TPromise<void> {
private _activateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): TPromise<void> {
if (hasOwnProperty.call(this._activatedExtensions, extensionDescription.id)) {
return TPromise.as(void 0);
}
@@ -311,7 +326,7 @@ export class ExtensionsActivator {
return this._activatingExtensions[extensionDescription.id];
}
this._activatingExtensions[extensionDescription.id] = this._host.actualActivateExtension(extensionDescription, startup).then(null, (err) => {
this._activatingExtensions[extensionDescription.id] = this._host.actualActivateExtension(extensionDescription, reason).then(null, (err) => {
this._host.showMessage(Severity.Error, nls.localize('activationError', "Activating extension `{0}` failed: {1}.", extensionDescription.id, err.message));
console.error('Activating extension `' + extensionDescription.id + '` failed: ', err.message);
console.log('Here is the error stack: ', err.stack);

View File

@@ -14,7 +14,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'
import { ExtHostStorage } from 'vs/workbench/api/node/extHostStorage';
import { createApiFactory, initializeExtensionApi } from 'vs/workbench/api/node/extHost.api.impl';
import { MainContext, MainThreadExtensionServiceShape, IWorkspaceData, IEnvironment, IInitData, ExtHostExtensionServiceShape, MainThreadTelemetryShape } from './extHost.protocol';
import { IExtensionMemento, ExtensionsActivator, ActivatedExtension, IExtensionAPI, IExtensionContext, EmptyExtension, IExtensionModule, ExtensionActivationTimesBuilder, ExtensionActivationTimes } from 'vs/workbench/api/node/extHostExtensionActivator';
import { IExtensionMemento, ExtensionsActivator, ActivatedExtension, IExtensionAPI, IExtensionContext, EmptyExtension, IExtensionModule, ExtensionActivationTimesBuilder, ExtensionActivationTimes, ExtensionActivationReason, ExtensionActivatedByEvent } from 'vs/workbench/api/node/extHostExtensionActivator';
import { ExtHostThreadService } from 'vs/workbench/services/thread/node/extHostThreadService';
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
@@ -159,8 +159,8 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
}
},
actualActivateExtension: (extensionDescription: IExtensionDescription, startup: boolean): TPromise<ActivatedExtension> => {
return this._activateExtension(extensionDescription, startup);
actualActivateExtension: (extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): TPromise<ActivatedExtension> => {
return this._activateExtension(extensionDescription, reason);
}
});
@@ -180,18 +180,19 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
}
public activateByEvent(activationEvent: string, startup: boolean): TPromise<void> {
const reason = new ExtensionActivatedByEvent(startup, activationEvent);
if (this._barrier.isOpen()) {
return this._activator.activateByEvent(activationEvent, startup);
return this._activator.activateByEvent(activationEvent, reason);
} else {
return this._barrier.wait().then(() => this._activator.activateByEvent(activationEvent, startup));
return this._barrier.wait().then(() => this._activator.activateByEvent(activationEvent, reason));
}
}
public activateById(extensionId: string, startup: boolean): TPromise<void> {
public activateById(extensionId: string, reason: ExtensionActivationReason): TPromise<void> {
if (this._barrier.isOpen()) {
return this._activator.activateById(extensionId, startup);
return this._activator.activateById(extensionId, reason);
} else {
return this._barrier.wait().then(() => this._activator.activateById(extensionId, startup));
return this._barrier.wait().then(() => this._activator.activateById(extensionId, reason));
}
}
@@ -274,12 +275,17 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
return result;
}
public addMessage(extensionId: string, severity: Severity, message: string): void {
this._proxy.$addMessage(extensionId, severity, message);
}
// --- impl
private _activateExtension(extensionDescription: IExtensionDescription, startup: boolean): TPromise<ActivatedExtension> {
return this._doActivateExtension(extensionDescription, startup).then((activatedExtension) => {
private _activateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): TPromise<ActivatedExtension> {
return this._doActivateExtension(extensionDescription, reason).then((activatedExtension) => {
const activationTimes = activatedExtension.activationTimes;
this._proxy.$onExtensionActivated(extensionDescription.id, activationTimes.startup, activationTimes.codeLoadingTime, activationTimes.activateCallTime, activationTimes.activateResolvedTime);
let activationEvent = (reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : null);
this._proxy.$onExtensionActivated(extensionDescription.id, activationTimes.startup, activationTimes.codeLoadingTime, activationTimes.activateCallTime, activationTimes.activateResolvedTime, activationEvent);
return activatedExtension;
}, (err) => {
this._proxy.$onExtensionActivationFailed(extensionDescription.id);
@@ -287,7 +293,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
});
}
private _doActivateExtension(extensionDescription: IExtensionDescription, startup: boolean): TPromise<ActivatedExtension> {
private _doActivateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): TPromise<ActivatedExtension> {
let event = getTelemetryActivationEvent(extensionDescription);
/* __GDPR__
"activatePlugin" : {
@@ -302,7 +308,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
return TPromise.as(new EmptyExtension(ExtensionActivationTimes.NONE));
}
const activationTimesBuilder = new ExtensionActivationTimesBuilder(startup);
const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup);
return TPromise.join<any>([
loadCommonJSModule(extensionDescription.main, activationTimesBuilder),
this._loadExtensionContext(extensionDescription)
@@ -401,13 +407,7 @@ function getTelemetryActivationEvent(extensionDescription: IExtensionDescription
"name": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
"publisherDisplayName": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
"activationEvents": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"isBuiltin": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"${wildcard}": [
{
"${prefix}": "contribution.",
"${classification}": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
]
"isBuiltin": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
let event = {
@@ -418,34 +418,5 @@ function getTelemetryActivationEvent(extensionDescription: IExtensionDescription
isBuiltin: extensionDescription.isBuiltin
};
for (let contribution in extensionDescription.contributes) {
let contributionDetails = extensionDescription.contributes[contribution];
if (!contributionDetails) {
continue;
}
switch (contribution) {
case 'debuggers':
let types = contributionDetails.reduce((p, c) => p ? p + ',' + c['type'] : c['type'], '');
event['contribution.debuggers'] = types;
break;
case 'grammars':
let grammers = contributionDetails.reduce((p, c) => p ? p + ',' + c['language'] : c['language'], '');
event['contribution.grammars'] = grammers;
break;
case 'languages':
let languages = contributionDetails.reduce((p, c) => p ? p + ',' + c['id'] : c['id'], '');
event['contribution.languages'] = languages;
break;
case 'tmSnippets':
let tmSnippets = contributionDetails.reduce((p, c) => p ? p + ',' + c['languageId'] : c['languageId'], '');
event['contribution.tmSnippets'] = tmSnippets;
break;
default:
event[`contribution.${contribution}`] = true;
}
}
return event;
}

View File

@@ -20,13 +20,13 @@ export class ExtHostEditors implements ExtHostEditorsShape {
private readonly _onDidChangeTextEditorSelection = new Emitter<vscode.TextEditorSelectionChangeEvent>();
private readonly _onDidChangeTextEditorOptions = new Emitter<vscode.TextEditorOptionsChangeEvent>();
private readonly _onDidChangeTextEditorViewColumn = new Emitter<vscode.TextEditorViewColumnChangeEvent>();
private readonly _onDidChangeActiveTextEditor = new Emitter<vscode.TextEditor>();
private readonly _onDidChangeActiveTextEditor = new Emitter<vscode.TextEditor | undefined>();
private readonly _onDidChangeVisibleTextEditors = new Emitter<vscode.TextEditor[]>();
readonly onDidChangeTextEditorSelection: Event<vscode.TextEditorSelectionChangeEvent> = this._onDidChangeTextEditorSelection.event;
readonly onDidChangeTextEditorOptions: Event<vscode.TextEditorOptionsChangeEvent> = this._onDidChangeTextEditorOptions.event;
readonly onDidChangeTextEditorViewColumn: Event<vscode.TextEditorViewColumnChangeEvent> = this._onDidChangeTextEditorViewColumn.event;
readonly onDidChangeActiveTextEditor: Event<vscode.TextEditor> = this._onDidChangeActiveTextEditor.event;
readonly onDidChangeActiveTextEditor: Event<vscode.TextEditor | undefined> = this._onDidChangeActiveTextEditor.event;
readonly onDidChangeVisibleTextEditors: Event<vscode.TextEditor[]> = this._onDidChangeVisibleTextEditors.event;

View File

@@ -7,17 +7,15 @@
import { localize } from 'vs/nls';
import * as vscode from 'vscode';
import URI from 'vs/base/common/uri';
import { distinct } from 'vs/base/common/arrays';
import { debounceEvent } from 'vs/base/common/event';
import { TPromise } from 'vs/base/common/winjs.base';
import { Disposable } from 'vs/base/common/lifecycle';
import { ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.protocol';
import { ITreeItem, TreeViewItemHandleArg } from 'vs/workbench/common/views';
import { TreeItemCollapsibleState } from './extHostTypes';
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands';
import { asWinJsPromise } from 'vs/base/common/async';
type TreeItemHandle = number;
type TreeItemHandle = string;
export class ExtHostTreeViews implements ExtHostTreeViewsShape {
@@ -56,7 +54,7 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
return treeView.getTreeItems();
}
$getChildren(treeViewId: string, treeItemHandle?: number): TPromise<ITreeItem[]> {
$getChildren(treeViewId: string, treeItemHandle?: string): TPromise<ITreeItem[]> {
const treeView = this.treeViews.get(treeViewId);
if (!treeView) {
return TPromise.wrapError<ITreeItem[]>(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId)));
@@ -70,13 +68,18 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
}
}
interface TreeNode {
index: number;
handle: TreeItemHandle;
parent: TreeItemHandle;
children: TreeItemHandle[];
}
class ExtHostTreeView<T> extends Disposable {
private _itemHandlePool = 0;
private extElementsMap: Map<TreeItemHandle, T> = new Map<TreeItemHandle, T>();
private itemHandlesMap: Map<T, TreeItemHandle> = new Map<T, TreeItemHandle>();
private extChildrenElementsMap: Map<T, T[]> = new Map<T, T[]>();
private static ROOT_HANDLE = '0';
private elements: Map<TreeItemHandle, T> = new Map<TreeItemHandle, T>();
private nodes: Map<T, TreeNode> = new Map<T, TreeNode>();
constructor(private viewId: string, private dataProvider: vscode.TreeDataProvider<T>, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter) {
super();
@@ -87,12 +90,9 @@ class ExtHostTreeView<T> extends Disposable {
}
getTreeItems(): TPromise<ITreeItem[]> {
this.extChildrenElementsMap.clear();
this.extElementsMap.clear();
this.itemHandlesMap.clear();
this.clearAll();
return asWinJsPromise(() => this.dataProvider.getChildren())
.then(elements => this.processAndMapElements(elements));
.then(elements => this.resolveElements(elements));
}
getChildren(treeItemHandle: TreeItemHandle): TPromise<ITreeItem[]> {
@@ -104,74 +104,72 @@ class ExtHostTreeView<T> extends Disposable {
}
return asWinJsPromise(() => this.dataProvider.getChildren(extElement))
.then(childrenElements => this.processAndMapElements(childrenElements));
.then(childrenElements => this.resolveElements(childrenElements, treeItemHandle))
.then(childrenItems => {
this.nodes.get(extElement).children = childrenItems.map(c => c.handle);
return childrenItems;
});
}
getExtensionElement(treeItemHandle: TreeItemHandle): T {
return this.extElementsMap.get(treeItemHandle);
return this.elements.get(treeItemHandle);
}
private _refresh(elements: T[]): void {
const hasRoot = elements.some(element => !element);
if (hasRoot) {
this.proxy.$refresh(this.viewId, []);
this.proxy.$refresh(this.viewId);
} else {
const itemHandles = distinct(elements.map(element => this.itemHandlesMap.get(element))
.filter(itemHandle => !!itemHandle));
if (itemHandles.length) {
this.proxy.$refresh(this.viewId, itemHandles);
const handlesToUpdate = this.getHandlesToUpdate(elements);
if (handlesToUpdate.length) {
this._refreshHandles(handlesToUpdate);
}
}
}
private processAndMapElements(elements: T[]): TPromise<ITreeItem[]> {
private resolveElements(elements: T[], parentHandle?: TreeItemHandle): TPromise<ITreeItem[]> {
if (elements && elements.length) {
return TPromise.join(
elements.filter(element => !!element)
.map(element => {
if (this.extChildrenElementsMap.has(element)) {
return TPromise.wrapError<ITreeItem>(new Error(localize('treeView.duplicateElement', 'Element {0} is already registered', element)));
}
return this.resolveElement(element);
.map((element, index) => {
return this.resolveElement(element, index, parentHandle)
.then(treeItem => {
if (treeItem) {
this.nodes.set(element, {
index,
handle: treeItem.handle,
parent: parentHandle,
children: void 0
});
this.elements.set(treeItem.handle, element);
}
return treeItem;
});
}))
.then(treeItems => treeItems.filter(treeItem => !!treeItem));
}
return TPromise.as([]);
}
private resolveElement(element: T): TPromise<ITreeItem> {
private resolveElement(element: T, index: number, parentHandle?: TreeItemHandle): TPromise<ITreeItem> {
return asWinJsPromise(() => this.dataProvider.getTreeItem(element))
.then(extTreeItem => {
const treeItem = this.massageTreeItem(extTreeItem);
if (treeItem) {
this.itemHandlesMap.set(element, treeItem.handle);
this.extElementsMap.set(treeItem.handle, element);
if (treeItem.collapsibleState === TreeItemCollapsibleState.Expanded) {
return this.getChildren(treeItem.handle).then(children => {
treeItem.children = children;
return treeItem;
});
} else {
return treeItem;
}
}
return null;
});
.then(extTreeItem => this.massageTreeItem(extTreeItem, index, parentHandle));
}
private massageTreeItem(extensionTreeItem: vscode.TreeItem): ITreeItem {
private massageTreeItem(extensionTreeItem: vscode.TreeItem, index: number, parentHandle: TreeItemHandle): ITreeItem {
if (!extensionTreeItem) {
return null;
}
const icon = this.getLightIconPath(extensionTreeItem);
return {
handle: ++this._itemHandlePool,
handle: `${parentHandle ? parentHandle : ExtHostTreeView.ROOT_HANDLE}/${index}:${extensionTreeItem.label}`,
parentHandle,
label: extensionTreeItem.label,
command: extensionTreeItem.command ? this.commands.toInternal(extensionTreeItem.command) : void 0,
contextValue: extensionTreeItem.contextValue,
icon,
iconDark: this.getDarkIconPath(extensionTreeItem) || icon,
collapsibleState: extensionTreeItem.collapsibleState,
collapsibleState: extensionTreeItem.collapsibleState
};
}
@@ -199,29 +197,107 @@ class ExtHostTreeView<T> extends Disposable {
return URI.file(iconPath).toString();
}
private clearChildren(extElement: T): void {
const children = this.extChildrenElementsMap.get(extElement);
if (children) {
for (const child of children) {
this.clearElement(child);
private getHandlesToUpdate(elements: T[]): TreeItemHandle[] {
const elementsToUpdate = new Set<TreeItemHandle>();
for (const element of elements) {
let elementNode = this.nodes.get(element);
if (elementNode && !elementsToUpdate.has(elementNode.handle)) {
// check if an ancestor of extElement is already in the elements to update list
let currentNode = elementNode;
while (currentNode && currentNode.parent && !elementsToUpdate.has(currentNode.parent)) {
const parentElement = this.elements.get(currentNode.parent);
currentNode = this.nodes.get(parentElement);
}
if (!currentNode.parent) {
elementsToUpdate.add(elementNode.handle);
}
}
this.extChildrenElementsMap.delete(extElement);
}
const handlesToUpdate: TreeItemHandle[] = [];
// Take only top level elements
elementsToUpdate.forEach((handle) => {
const element = this.elements.get(handle);
let node = this.nodes.get(element);
if (node && !elementsToUpdate.has(node.parent)) {
handlesToUpdate.push(handle);
}
});
return handlesToUpdate;
}
private clearElement(extElement: T): void {
this.clearChildren(extElement);
private _refreshHandles(itemHandles: TreeItemHandle[]): TPromise<void> {
const itemsToRefresh: { [handle: string]: ITreeItem } = {};
const promises: TPromise<void>[] = [];
itemHandles.forEach(treeItemHandle => {
const extElement = this.getExtensionElement(treeItemHandle);
const node = this.nodes.get(extElement);
const promise = this.resolveElement(extElement, node.index, node.parent)
.then(treeItem => {
if (treeItemHandle !== treeItem.handle) {
// Update caches if handle changes
this.updateCaches(node, treeItem, extElement);
}
itemsToRefresh[treeItemHandle] = treeItem;
});
promises.push(promise);
});
return TPromise.join(promises)
.then(treeItems => {
this.proxy.$refresh(this.viewId, itemsToRefresh);
});
}
const treeItemhandle = this.itemHandlesMap.get(extElement);
this.itemHandlesMap.delete(extElement);
if (treeItemhandle) {
this.extElementsMap.delete(treeItemhandle);
private updateCaches(node: TreeNode, treeItem: ITreeItem, element: T): void {
if (node.parent) {
// Update parent's children handles
const parentElement = this.getExtensionElement(node.parent);
const parentNode = this.nodes.get(parentElement);
parentNode.children[node.index] = treeItem.handle;
}
// Update elements map
this.elements.delete(node.handle);
this.elements.set(treeItem.handle, element);
// Update node
node.handle = treeItem.handle;
}
private clearChildren(element: T): void {
let node = this.nodes.get(element);
if (node.children) {
for (const childHandle of node.children) {
const childEleement = this.elements.get(childHandle);
if (childEleement) {
this.clear(childEleement);
}
}
}
node.children = void 0;
}
private clear(element: T): void {
let node = this.nodes.get(element);
if (node.children) {
for (const childHandle of node.children) {
const childEleement = this.elements.get(childHandle);
if (childEleement) {
this.clear(childEleement);
}
}
}
this.nodes.delete(element);
this.elements.delete(node.handle);
}
private clearAll(): void {
this.elements.clear();
this.nodes.clear();
}
dispose() {
this.extElementsMap.clear();
this.itemHandlesMap.clear();
this.extChildrenElementsMap.clear();
this.clearAll();
}
}