Merge from vscode c873727e8bac95e7cbf5b154a9e6ae0986f2ce18 (#6446)

This commit is contained in:
Anthony Dresser
2019-07-19 19:21:54 -07:00
committed by GitHub
parent 5c63f7bdb5
commit b21435743d
63 changed files with 2049 additions and 1000 deletions

View File

@@ -31,7 +31,7 @@ import { LogLevel } from 'vs/platform/log/common/log';
import { IMarkerData } from 'vs/platform/markers/common/markers';
import { IProgressOptions, IProgressStep } from 'vs/platform/progress/common/progress';
import * as quickInput from 'vs/platform/quickinput/common/quickInput';
import { RemoteAuthorityResolverErrorCode, ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { RemoteAuthorityResolverErrorCode, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
import * as statusbar from 'vs/platform/statusbar/common/statusbar';
import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings';
import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry';
@@ -859,7 +859,7 @@ export interface IResolveAuthorityErrorResult {
export interface IResolveAuthorityOKResult {
type: 'ok';
value: ResolvedAuthority;
value: ResolverResult;
}
export type IResolveAuthorityResult = IResolveAuthorityErrorResult | IResolveAuthorityOKResult;

View File

@@ -6,7 +6,7 @@
import { URI, UriComponents } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { debounce } from 'vs/base/common/decorators';
import { dispose, IDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
import { asPromise } from 'vs/base/common/async';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, CommandDto } from './extHost.protocol';
@@ -263,7 +263,6 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG
}
readonly handle = ExtHostSourceControlResourceGroup._handlePool++;
private _disposables: IDisposable[] = [];
constructor(
private _proxy: MainThreadSCMShape,
@@ -353,7 +352,6 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG
dispose(): void {
this._proxy.$unregisterGroup(this._sourceControlHandle, this.handle);
this._disposables = dispose(this._disposables);
this._onDidDispose.fire();
}
}

View File

@@ -36,6 +36,7 @@ import { ExtensionMemento } from 'vs/workbench/api/common/extHostMemento';
import { ExtensionStoragePaths } from 'vs/workbench/api/node/extHostStoragePaths';
import { RemoteAuthorityResolverError, ExtensionExecutionContext } from 'vs/workbench/api/common/extHostTypes';
import { IURITransformer } from 'vs/base/common/uriIpc';
import { ResolvedAuthority, ResolvedOptions } from 'vs/platform/remote/common/remoteAuthorityResolver';
interface ITestRunner {
/** Old test runner API, as exported from `vscode/lib/testrunner` */
@@ -680,12 +681,22 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
try {
const result = await resolver.resolve(remoteAuthority, { resolveAttempt });
// Split merged API result into separate authority/options
const authority: ResolvedAuthority = {
authority: remoteAuthority,
host: result.host,
port: result.port
};
const options: ResolvedOptions = {
extensionHostEnv: result.extensionHostEnv
};
return {
type: 'ok',
value: {
authority: remoteAuthority,
host: result.host,
port: result.port,
authority,
options
}
};
} catch (err) {

View File

@@ -20,10 +20,10 @@ export class CommentsDataSource implements IDataSource {
return 'root';
}
if (element instanceof ResourceWithCommentThreads) {
return element.id;
return `${element.owner}-${element.id}`;
}
if (element instanceof CommentNode) {
return `${element.resource.toString()}-${element.comment.uniqueIdInThread}`;
return `${element.owner}-${element.resource.toString()}-${element.threadId}-${element.comment.uniqueIdInThread}`;
}
return '';
}

View File

@@ -15,13 +15,15 @@ export interface ICommentThreadChangedEvent extends CommentThreadChangedEvent {
}
export class CommentNode {
owner: string;
threadId: string;
range: IRange;
comment: Comment;
replies: CommentNode[] = [];
resource: URI;
constructor(threadId: string, resource: URI, comment: Comment, range: IRange) {
constructor(owner: string, threadId: string, resource: URI, comment: Comment, range: IRange) {
this.owner = owner;
this.threadId = threadId;
this.comment = comment;
this.resource = resource;
@@ -35,18 +37,20 @@ export class CommentNode {
export class ResourceWithCommentThreads {
id: string;
owner: string;
commentThreads: CommentNode[]; // The top level comments on the file. Replys are nested under each node.
resource: URI;
constructor(resource: URI, commentThreads: CommentThread[]) {
constructor(owner: string, resource: URI, commentThreads: CommentThread[]) {
this.owner = owner;
this.id = resource.toString();
this.resource = resource;
this.commentThreads = commentThreads.filter(thread => thread.comments && thread.comments.length).map(thread => ResourceWithCommentThreads.createCommentNode(resource, thread));
this.commentThreads = commentThreads.filter(thread => thread.comments && thread.comments.length).map(thread => ResourceWithCommentThreads.createCommentNode(owner, resource, thread));
}
public static createCommentNode(resource: URI, commentThread: CommentThread): CommentNode {
public static createCommentNode(owner: string, resource: URI, commentThread: CommentThread): CommentNode {
const { threadId, comments, range } = commentThread;
const commentNodes: CommentNode[] = comments!.map(comment => new CommentNode(threadId!, resource, comment, range));
const commentNodes: CommentNode[] = comments!.map(comment => new CommentNode(owner, threadId!, resource, comment, range));
if (commentNodes.length > 1) {
commentNodes[0].replies = commentNodes.slice(1, commentNodes.length);
}
@@ -65,7 +69,7 @@ export class CommentsModel {
}
public setCommentThreads(owner: string, commentThreads: CommentThread[]): void {
this.commentThreadsMap.set(owner, this.groupByResource(commentThreads));
this.commentThreadsMap.set(owner, this.groupByResource(owner, commentThreads));
this.resourceCommentThreads = flatten(values(this.commentThreadsMap));
}
@@ -97,9 +101,9 @@ export class CommentsModel {
// Find comment node on resource that is that thread and replace it
const index = firstIndex(matchingResourceData.commentThreads, (commentThread) => commentThread.threadId === thread.threadId);
if (index >= 0) {
matchingResourceData.commentThreads[index] = ResourceWithCommentThreads.createCommentNode(URI.parse(matchingResourceData.id), thread);
matchingResourceData.commentThreads[index] = ResourceWithCommentThreads.createCommentNode(owner, URI.parse(matchingResourceData.id), thread);
} else if (thread.comments && thread.comments.length) {
matchingResourceData.commentThreads.push(ResourceWithCommentThreads.createCommentNode(URI.parse(matchingResourceData.id), thread));
matchingResourceData.commentThreads.push(ResourceWithCommentThreads.createCommentNode(owner, URI.parse(matchingResourceData.id), thread));
}
});
@@ -108,10 +112,10 @@ export class CommentsModel {
if (existingResource.length) {
const resource = existingResource[0];
if (thread.comments && thread.comments.length) {
resource.commentThreads.push(ResourceWithCommentThreads.createCommentNode(resource.resource, thread));
resource.commentThreads.push(ResourceWithCommentThreads.createCommentNode(owner, resource.resource, thread));
}
} else {
threadsForOwner.push(new ResourceWithCommentThreads(URI.parse(thread.resource!), [thread]));
threadsForOwner.push(new ResourceWithCommentThreads(owner, URI.parse(thread.resource!), [thread]));
}
});
@@ -133,11 +137,11 @@ export class CommentsModel {
}
}
private groupByResource(commentThreads: CommentThread[]): ResourceWithCommentThreads[] {
private groupByResource(owner: string, commentThreads: CommentThread[]): ResourceWithCommentThreads[] {
const resourceCommentThreads: ResourceWithCommentThreads[] = [];
const commentThreadsByResource = new Map<string, ResourceWithCommentThreads>();
for (const group of groupBy(commentThreads, CommentsModel._compareURIs)) {
commentThreadsByResource.set(group[0].resource!, new ResourceWithCommentThreads(URI.parse(group[0].resource!), group));
commentThreadsByResource.set(group[0].resource!, new ResourceWithCommentThreads(owner, URI.parse(group[0].resource!), group));
}
commentThreadsByResource.forEach((v, i, m) => {

View File

@@ -10,17 +10,17 @@ import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { language } from 'vs/base/common/platform';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Disposable } from 'vs/base/common/lifecycle';
import { match } from 'vs/base/common/glob';
import { IRequestService, asJson } from 'vs/platform/request/common/request';
import { Emitter, Event } from 'vs/base/common/event';
import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/common/textfiles';
import { WorkspaceStats } from 'vs/workbench/contrib/stats/electron-browser/workspaceStats';
import { CancellationToken } from 'vs/base/common/cancellation';
import { distinct } from 'vs/base/common/arrays';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { ExperimentState, IExperimentAction, IExperimentService, IExperiment, ExperimentActionType, IExperimentActionPromptProperties } from 'vs/workbench/contrib/experiments/common/experimentService';
import { IProductService } from 'vs/platform/product/common/product';
import { IWorkspaceStatsService } from 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService';
interface IExperimentStorageState {
enabled: boolean;
@@ -60,7 +60,6 @@ export class ExperimentService extends Disposable implements IExperimentService
private _experiments: IExperiment[] = [];
private _loadExperimentsPromise: Promise<void>;
private _curatedMapping = Object.create(null);
private _disposables: IDisposable[] = [];
private readonly _onExperimentEnabled = this._register(new Emitter<IExperiment>());
onExperimentEnabled: Event<IExperiment> = this._onExperimentEnabled.event;
@@ -74,7 +73,8 @@ export class ExperimentService extends Disposable implements IExperimentService
@ILifecycleService private readonly lifecycleService: ILifecycleService,
@IRequestService private readonly requestService: IRequestService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IProductService private readonly productService: IProductService
@IProductService private readonly productService: IProductService,
@IWorkspaceStatsService private readonly workspaceStatsService: IWorkspaceStatsService
) {
super();
@@ -355,7 +355,7 @@ export class ExperimentService extends Disposable implements IExperimentService
onSaveHandler.dispose();
return;
}
e.forEach(event => {
e.forEach(async event => {
if (event.kind !== StateChange.SAVED
|| latestExperimentState.state !== ExperimentState.Evaluating
|| date === latestExperimentState.lastEditedDate
@@ -370,10 +370,12 @@ export class ExperimentService extends Disposable implements IExperimentService
filePathCheck = match(fileEdits.filePathPattern, event.resource.fsPath);
}
if (Array.isArray(fileEdits.workspaceIncludes) && fileEdits.workspaceIncludes.length) {
workspaceCheck = !!WorkspaceStats.TAGS && fileEdits.workspaceIncludes.some(x => !!WorkspaceStats.TAGS[x]);
const tags = await this.workspaceStatsService.getTags();
workspaceCheck = !!tags && fileEdits.workspaceIncludes.some(x => !!tags[x]);
}
if (workspaceCheck && Array.isArray(fileEdits.workspaceExcludes) && fileEdits.workspaceExcludes.length) {
workspaceCheck = !!WorkspaceStats.TAGS && !fileEdits.workspaceExcludes.some(x => !!WorkspaceStats.TAGS[x]);
const tags = await this.workspaceStatsService.getTags();
workspaceCheck = !!tags && !fileEdits.workspaceExcludes.some(x => !!tags[x]);
}
if (filePathCheck && workspaceCheck) {
latestExperimentState.editCount = (latestExperimentState.editCount || 0) + 1;
@@ -389,14 +391,10 @@ export class ExperimentService extends Disposable implements IExperimentService
}
}
});
this._disposables.push(onSaveHandler);
this._register(onSaveHandler);
return ExperimentState.Evaluating;
});
}
dispose() {
this._disposables = dispose(this._disposables);
}
}

View File

@@ -17,7 +17,7 @@ import { dispose, Disposable } from 'vs/base/common/lifecycle';
import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG, ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/workbench/contrib/extensions/common/extensions';
import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate';
import { ExtensionsLabel, IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionTipsService, IExtensionRecommendation, IExtensionsConfigContent } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionTipsService, IExtensionRecommendation, IExtensionsConfigContent, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
@@ -176,19 +176,22 @@ export class InstallAction extends ExtensionAction {
}
update(): void {
if (!this.extension || this.extension.type === ExtensionType.System || this.extension.state === ExtensionState.Installed) {
this.enabled = false;
this.class = InstallAction.Class;
this.label = InstallAction.INSTALL_LABEL;
return;
}
this.enabled = false;
if (this.extensionsWorkbenchService.canInstall(this.extension)) {
const local = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier))[0];
this.enabled = !local || (!!local.local && isLanguagePackExtension(local.local.manifest));
this.class = InstallAction.Class;
this.label = InstallAction.INSTALL_LABEL;
if (this.extension && this.extension.type === ExtensionType.User) {
if (this.extension.state === ExtensionState.Uninstalled && this.extensionsWorkbenchService.canInstall(this.extension)) {
this.enabled = true;
this.updateLabel();
return;
}
if (this.extension.state === ExtensionState.Installing) {
this.enabled = false;
this.updateLabel();
this.class = this.extension.state === ExtensionState.Installing ? InstallAction.InstallingClass : InstallAction.Class;
return;
}
}
this.class = this.extension.state === ExtensionState.Installing ? InstallAction.InstallingClass : InstallAction.Class;
this.updateLabel();
}
private updateLabel(): void {
@@ -283,60 +286,55 @@ export class InstallAction extends ExtensionAction {
}
}
export class RemoteInstallAction extends ExtensionAction {
export class InstallInOtherServerAction extends ExtensionAction {
private static INSTALL_LABEL = localize('install', "Install");
private static INSTALLING_LABEL = localize('installing', "Installing");
protected static INSTALL_LABEL = localize('install', "Install");
protected static INSTALLING_LABEL = localize('installing', "Installing");
private static readonly Class = 'extension-action prominent install';
private static readonly InstallingClass = 'extension-action install installing';
updateWhenCounterExtensionChanges: boolean = true;
private installing: boolean = false;
protected installing: boolean = false;
constructor(
id: string,
private readonly server: IExtensionManagementServer | null,
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
@ILabelService private readonly labelService: ILabelService,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IProductService private readonly productService: IProductService,
) {
super(`extensions.remoteinstall`, RemoteInstallAction.INSTALL_LABEL, RemoteInstallAction.Class, false);
this._register(this.labelService.onDidChangeFormatters(() => this.updateLabel(), this));
super(id, InstallInOtherServerAction.INSTALL_LABEL, InstallInOtherServerAction.Class, false);
this.updateLabel();
this.update();
}
private updateLabel(): void {
if (this.installing) {
this.label = RemoteInstallAction.INSTALLING_LABEL;
this.tooltip = this.label;
return;
}
const remoteServer = this.extensionManagementServerService.remoteExtensionManagementServer;
if (remoteServer) {
this.label = `${RemoteInstallAction.INSTALL_LABEL} on ${remoteServer.label}`;
this.tooltip = this.label;
return;
}
this.label = this.getLabel();
this.tooltip = this.label;
}
protected getLabel(): string {
return this.installing ? InstallInOtherServerAction.INSTALLING_LABEL :
this.server ? `${InstallInOtherServerAction.INSTALL_LABEL} on ${this.server.label}`
: InstallInOtherServerAction.INSTALL_LABEL;
}
update(): void {
this.enabled = false;
this.class = RemoteInstallAction.Class;
this.class = InstallInOtherServerAction.Class;
if (this.installing) {
this.enabled = true;
this.class = RemoteInstallAction.InstallingClass;
this.class = InstallInOtherServerAction.InstallingClass;
this.updateLabel();
return;
}
if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer
// Installed User Extension
&& this.extension && this.extension.local && this.extension.type === ExtensionType.User && this.extension.state === ExtensionState.Installed
// Local Workspace Extension
&& this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && (isLanguagePackExtension(this.extension.local.manifest) || !isUIExtension(this.extension.local.manifest, this.productService, this.configurationService))
// Extension does not exist in remote
&& !this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.extensionManagementServerService.remoteExtensionManagementServer)
if (
this.extension && this.extension.local && this.server && this.extension.state === ExtensionState.Installed
// disabled by extension kind or it is a language pack extension
&& (this.extension.enablementState === EnablementState.DisabledByExtensionKind || isLanguagePackExtension(this.extension.local.manifest))
// Not installed in other server and can install in other server
&& !this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.server)
&& this.extensionsWorkbenchService.canInstall(this.extension)
) {
this.enabled = true;
@@ -346,13 +344,13 @@ export class RemoteInstallAction extends ExtensionAction {
}
async run(): Promise<void> {
if (this.extensionManagementServerService.remoteExtensionManagementServer && !this.installing) {
if (this.server && !this.installing) {
this.installing = true;
this.update();
this.extensionsWorkbenchService.open(this.extension);
alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName));
if (this.extension.gallery) {
await this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.installFromGallery(this.extension.gallery);
await this.server.extensionManagementService.installFromGallery(this.extension.gallery);
this.installing = false;
this.update();
}
@@ -360,77 +358,30 @@ export class RemoteInstallAction extends ExtensionAction {
}
}
export class LocalInstallAction extends ExtensionAction {
private static INSTALL_LABEL = localize('install locally', "Install Locally");
private static INSTALLING_LABEL = localize('installing', "Installing");
private static readonly Class = 'extension-action prominent install';
private static readonly InstallingClass = 'extension-action install installing';
updateWhenCounterExtensionChanges: boolean = true;
private installing: boolean = false;
export class RemoteInstallAction extends InstallInOtherServerAction {
constructor(
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
@ILabelService private readonly labelService: ILabelService,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IProductService private readonly productService: IProductService,
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService
) {
super(`extensions.localinstall`, LocalInstallAction.INSTALL_LABEL, LocalInstallAction.Class, false);
this._register(this.labelService.onDidChangeFormatters(() => this.updateLabel(), this));
this.updateLabel();
this.update();
super(`extensions.remoteinstall`, extensionManagementServerService.remoteExtensionManagementServer, extensionsWorkbenchService);
}
private updateLabel(): void {
if (this.installing) {
this.label = LocalInstallAction.INSTALLING_LABEL;
this.tooltip = this.label;
return;
}
this.label = `${LocalInstallAction.INSTALL_LABEL}`;
this.tooltip = this.label;
}
export class LocalInstallAction extends InstallInOtherServerAction {
constructor(
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService
) {
super(`extensions.localinstall`, extensionManagementServerService.localExtensionManagementServer, extensionsWorkbenchService);
}
update(): void {
this.enabled = false;
this.class = LocalInstallAction.Class;
if (this.installing) {
this.enabled = true;
this.class = LocalInstallAction.InstallingClass;
this.updateLabel();
return;
}
if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer
// Installed User Extension
&& this.extension && this.extension.local && this.extension.type === ExtensionType.User && this.extension.state === ExtensionState.Installed
// Remote UI or Language pack Extension
&& this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && (isLanguagePackExtension(this.extension.local.manifest) || isUIExtension(this.extension.local.manifest, this.productService, this.configurationService))
// Extension does not exist in local
&& !this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.extensionManagementServerService.localExtensionManagementServer)
&& this.extensionsWorkbenchService.canInstall(this.extension)
) {
this.enabled = true;
this.updateLabel();
return;
}
protected getLabel(): string {
return this.installing ? InstallInOtherServerAction.INSTALLING_LABEL : localize('install locally', "Install Locally");
}
async run(): Promise<void> {
if (this.extensionManagementServerService.localExtensionManagementServer && !this.installing) {
this.installing = true;
this.update();
this.extensionsWorkbenchService.open(this.extension);
alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName));
if (this.extension.gallery) {
await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(this.extension.gallery);
this.installing = false;
this.update();
}
}
}
}
export class UninstallAction extends ExtensionAction {

View File

@@ -64,8 +64,8 @@ class Extension implements IExtension {
@IProductService private readonly productService: IProductService
) { }
get type(): ExtensionType | undefined {
return this.local ? this.local.type : undefined;
get type(): ExtensionType {
return this.local ? this.local.type : ExtensionType.User;
}
get name(): string {

View File

@@ -34,7 +34,7 @@ export const enum ExtensionState {
}
export interface IExtension {
readonly type?: ExtensionType;
readonly type: ExtensionType;
readonly state: ExtensionState;
readonly name: string;
readonly displayName: string;

View File

@@ -21,7 +21,7 @@ import { TestExtensionEnablementService } from 'vs/workbench/services/extensionM
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
import { IURLService } from 'vs/platform/url/common/url';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { Emitter } from 'vs/base/common/event';
import { Emitter, Event } from 'vs/base/common/event';
import { IPager } from 'vs/base/common/paging';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
@@ -42,6 +42,9 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { ILabelService } from 'vs/platform/label/common/label';
import { ExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService';
import { IProductService } from 'vs/platform/product/common/product';
import { Schemas } from 'vs/base/common/network';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
suite('ExtensionsActions Test', () => {
@@ -53,7 +56,7 @@ suite('ExtensionsActions Test', () => {
didUninstallEvent: Emitter<DidUninstallExtensionEvent>;
suiteSetup(() => {
setup(async () => {
installEvent = new Emitter<InstallExtensionEvent>();
didInstallEvent = new Emitter<DidInstallExtensionEvent>();
uninstallEvent = new Emitter<IExtensionIdentifier>();
@@ -91,9 +94,7 @@ suite('ExtensionsActions Test', () => {
instantiationService.set(IExtensionTipsService, instantiationService.createInstance(ExtensionTipsService));
instantiationService.stub(IURLService, URLService);
});
setup(async () => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []);
instantiationService.stubPromise(IExtensionManagementService, 'getExtensionsReport', []);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage());
@@ -1357,6 +1358,682 @@ suite('ExtensionsActions Test', () => {
assert.ok(!testObject.enabled);
});
test('Test remote install action is enabled for local workspace extension', async () => {
// multi server setup
const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = extensions[0];
assert.ok(testObject.enabled);
assert.equal('Install on remote', testObject.label);
assert.equal('extension-action prominent install', testObject.class);
});
test('Test remote install action when installing local workspace extension', async (done) => {
// multi server setup
const remoteExtensionManagementService: IExtensionManagementService = createExtensionManagementService();
const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]), remoteExtensionManagementService);
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.stub(IExtensionsWorkbenchService, workbenchService, 'open', undefined);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = extensions[0];
assert.ok(testObject.enabled);
assert.equal('Install on remote', testObject.label);
assert.equal('extension-action prominent install', testObject.class);
remoteExtensionManagementService.installFromGallery = () => new Promise<ILocalExtension>(c => c(aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) })));
const disposable = testObject.onDidChange(() => {
if (testObject.label === 'Installing' && testObject.enabled) {
disposable.dispose();
done();
}
});
testObject.run();
});
test('Test remote install action when installing local workspace extension is finished', async (done) => {
// multi server setup
const remoteExtensionManagementService: IExtensionManagementService = createExtensionManagementService();
const onDidInstallEvent = new Emitter<DidInstallExtensionEvent>();
remoteExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event;
const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]), remoteExtensionManagementService);
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.stub(IExtensionsWorkbenchService, workbenchService, 'open', undefined);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = extensions[0];
assert.ok(testObject.enabled);
assert.equal('Install on remote', testObject.label);
assert.equal('extension-action prominent install', testObject.class);
const installedExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
remoteExtensionManagementService.installFromGallery = () => new Promise<ILocalExtension>(c => c(installedExtension));
await testObject.run();
assert.ok(testObject.enabled);
assert.equal('Install on remote', testObject.label);
const disposable = testObject.onDidChange(() => {
if (testObject.label === 'Install on remote' && !testObject.enabled) {
disposable.dispose();
done();
}
});
onDidInstallEvent.fire({ identifier: installedExtension.identifier, local: installedExtension, operation: InstallOperation.Install });
});
test('Test remote install action is enabled for disabled local workspace extension', async () => {
// multi server setup
const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
await instantiationService.get(IExtensionEnablementService).setEnablement([localWorkspaceExtension], EnablementState.DisabledGlobally);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = extensions[0];
assert.ok(testObject.enabled);
assert.equal('Install on remote', testObject.label);
assert.equal('extension-action prominent install', testObject.class);
});
test('Test remote install action is disabled when extension is not set', async () => {
// multi server setup
const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]));
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
assert.ok(!testObject.enabled);
});
test('Test remote install action is disabled for extension which is not installed', async () => {
// multi server setup
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService);
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a')));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const pager = await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = pager.firstPage[0];
assert.ok(testObject.extension);
assert.ok(!testObject.enabled);
});
test('Test remote install action is disabled for local workspace extension which is disabled in env', async () => {
// multi server setup
const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]));
instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true } as IWorkbenchEnvironmentService);
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = extensions[0];
assert.ok(testObject.extension);
assert.ok(!testObject.enabled);
});
test('Test remote install action is disabled when remote server is not available', async () => {
// single server setup
const workbenchService = instantiationService.get(IExtensionsWorkbenchService);
const extensionManagementServerService = instantiationService.get(IExtensionManagementServerService);
const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) });
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localWorkspaceExtension]);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = extensions[0];
assert.ok(testObject.extension);
assert.ok(!testObject.enabled);
});
test('Test remote install action is disabled for local workspace extension if it is uninstalled locally', async () => {
// multi server setup
const extensionManagementService = instantiationService.get(IExtensionManagementService);
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, extensionManagementService);
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) });
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localWorkspaceExtension]);
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = extensions[0];
assert.ok(testObject.enabled);
assert.equal('Install on remote', testObject.label);
uninstallEvent.fire(localWorkspaceExtension.identifier);
assert.ok(!testObject.enabled);
});
test('Test remote install action is disabled for local workspace extension if it is installed in remote', async () => {
// multi server setup
const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) });
const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]), createExtensionManagementService([remoteWorkspaceExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = extensions[0];
assert.ok(testObject.extension);
assert.ok(!testObject.enabled);
});
test('Test remote install action is disabled for local workspace extension if it cannot be installed', async () => {
// multi server setup
const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!);
testObject.extension = extensions[0];
assert.ok(testObject.extension);
assert.ok(!testObject.enabled);
});
test('Test remote install action is disabled for local ui extension if it is not installed in remote', async () => {
// multi server setup
const localUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localUIExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localUIExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!);
testObject.extension = extensions[0];
assert.ok(testObject.extension);
assert.ok(!testObject.enabled);
});
test('Test remote install action is disabled for local ui extension if it is also installed in remote', async () => {
// multi server setup
const localUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) });
const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localUIExtension]), createExtensionManagementService([remoteUIExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localUIExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!);
testObject.extension = extensions[0];
assert.ok(testObject.extension);
assert.ok(!testObject.enabled);
});
test('Test remote install action is enabled for locally installed language pack extension', async () => {
// multi server setup
const languagePackExtension = aLocalExtension('a', { contributes: <IExtensionContributions>{ localizations: [{ languageId: 'de', translations: [] }] } }, { location: URI.file(`pub.a`) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([languagePackExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: languagePackExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = extensions[0];
assert.ok(testObject.enabled);
assert.equal('Install on remote', testObject.label);
assert.equal('extension-action prominent install', testObject.class);
});
test('Test remote install action is disabled if local language pack extension is uninstalled', async () => {
// multi server setup
const extensionManagementService = instantiationService.get(IExtensionManagementService);
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, extensionManagementService);
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const languagePackExtension = aLocalExtension('a', { contributes: <IExtensionContributions>{ localizations: [{ languageId: 'de', translations: [] }] } }, { location: URI.file(`pub.a`) });
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [languagePackExtension]);
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: languagePackExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = extensions[0];
assert.ok(testObject.enabled);
assert.equal('Install on remote', testObject.label);
uninstallEvent.fire(languagePackExtension.identifier);
assert.ok(!testObject.enabled);
});
test('Test local install action is enabled for remote ui extension', async () => {
// multi server setup
const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = extensions[0];
assert.ok(testObject.enabled);
assert.equal('Install Locally', testObject.label);
assert.equal('extension-action prominent install', testObject.class);
});
test('Test local install action when installing remote ui extension', async (done) => {
// multi server setup
const localExtensionManagementService: IExtensionManagementService = createExtensionManagementService();
const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteUIExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.stub(IExtensionsWorkbenchService, workbenchService, 'open', undefined);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = extensions[0];
assert.ok(testObject.enabled);
assert.equal('Install Locally', testObject.label);
assert.equal('extension-action prominent install', testObject.class);
localExtensionManagementService.installFromGallery = () => new Promise<ILocalExtension>(c => c(aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) })));
const disposable = testObject.onDidChange(() => {
if (testObject.label === 'Installing' && testObject.enabled) {
disposable.dispose();
done();
}
});
testObject.run();
});
test('Test local install action when installing remote ui extension is finished', async (done) => {
// multi server setup
const localExtensionManagementService: IExtensionManagementService = createExtensionManagementService();
const onDidInstallEvent = new Emitter<DidInstallExtensionEvent>();
localExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event;
const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteUIExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.stub(IExtensionsWorkbenchService, workbenchService, 'open', undefined);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = extensions[0];
assert.ok(testObject.enabled);
assert.equal('Install Locally', testObject.label);
assert.equal('extension-action prominent install', testObject.class);
const installedExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) });
localExtensionManagementService.installFromGallery = () => new Promise<ILocalExtension>(c => c(installedExtension));
await testObject.run();
assert.ok(testObject.enabled);
assert.equal('Install Locally', testObject.label);
const disposable = testObject.onDidChange(() => {
if (testObject.label === 'Install Locally' && !testObject.enabled) {
disposable.dispose();
done();
}
});
onDidInstallEvent.fire({ identifier: installedExtension.identifier, local: installedExtension, operation: InstallOperation.Install });
});
test('Test local install action is enabled for disabled remote ui extension', async () => {
// multi server setup
const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
await instantiationService.get(IExtensionEnablementService).setEnablement([remoteUIExtension], EnablementState.DisabledGlobally);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = extensions[0];
assert.ok(testObject.enabled);
assert.equal('Install Locally', testObject.label);
assert.equal('extension-action prominent install', testObject.class);
});
test('Test local install action is disabled when extension is not set', async () => {
// multi server setup
const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension]));
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
assert.ok(!testObject.enabled);
});
test('Test local install action is disabled for extension which is not installed', async () => {
// multi server setup
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService);
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a')));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const pager = await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = pager.firstPage[0];
assert.ok(testObject.extension);
assert.ok(!testObject.enabled);
});
test('Test local install action is disabled for remote ui extension which is disabled in env', async () => {
// multi server setup
const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true } as IWorkbenchEnvironmentService);
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = extensions[0];
assert.ok(testObject.extension);
assert.ok(!testObject.enabled);
});
test('Test local install action is disabled when local server is not available', async () => {
// single server setup
const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aSingleRemoteExtensionManagementServerService(instantiationService, createExtensionManagementService([remoteUIExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = extensions[0];
assert.ok(testObject.extension);
assert.ok(!testObject.enabled);
});
test('Test local install action is disabled for remote ui extension if it is installed in local', async () => {
// multi server setup
const localUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) });
const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localUIExtension]), createExtensionManagementService([remoteUIExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localUIExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = extensions[0];
assert.ok(testObject.extension);
assert.ok(!testObject.enabled);
});
test('Test local install action is disabled for remoteUI extension if it is uninstalled locally', async () => {
// multi server setup
const extensionManagementService = instantiationService.get(IExtensionManagementService);
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), extensionManagementService);
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [remoteUIExtension]);
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = extensions[0];
assert.ok(testObject.enabled);
assert.equal('Install Locally', testObject.label);
uninstallEvent.fire(remoteUIExtension.identifier);
assert.ok(!testObject.enabled);
});
test('Test local install action is disabled for remote UI extension if it cannot be installed', async () => {
// multi server setup
const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!);
testObject.extension = extensions[0];
assert.ok(testObject.extension);
assert.ok(!testObject.enabled);
});
test('Test local install action is disabled for remote workspace extension if it is not installed in local', async () => {
// multi server setup
const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteWorkspaceExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteWorkspaceExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!);
testObject.extension = extensions[0];
assert.ok(testObject.extension);
assert.ok(!testObject.enabled);
});
test('Test local install action is disabled for remote workspace extension if it is also installed in local', async () => {
// multi server setup
const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspae' }, { location: URI.file(`pub.a`) });
const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]), createExtensionManagementService([remoteWorkspaceExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!);
testObject.extension = extensions[0];
assert.ok(testObject.extension);
assert.ok(!testObject.enabled);
});
test('Test local install action is enabled for remotely installed language pack extension', async () => {
// multi server setup
const languagePackExtension = aLocalExtension('a', { contributes: <IExtensionContributions>{ localizations: [{ languageId: 'de', translations: [] }] } }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([languagePackExtension]));
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: languagePackExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = extensions[0];
assert.ok(testObject.enabled);
assert.equal('Install Locally', testObject.label);
assert.equal('extension-action prominent install', testObject.class);
});
test('Test local install action is disabled if remote language pack extension is uninstalled', async () => {
// multi server setup
const extensionManagementService = instantiationService.get(IExtensionManagementService);
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), extensionManagementService);
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
const languagePackExtension = aLocalExtension('a', { contributes: <IExtensionContributions>{ localizations: [{ languageId: 'de', translations: [] }] } }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [languagePackExtension]);
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: languagePackExtension.identifier })));
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction);
instantiationService.createInstance(ExtensionContainers, [testObject]);
const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!);
await workbenchService.queryGallery(CancellationToken.None);
testObject.extension = extensions[0];
assert.ok(testObject.enabled);
assert.equal('Install Locally', testObject.label);
uninstallEvent.fire(languagePackExtension.identifier);
assert.ok(!testObject.enabled);
});
test(`RecommendToFolderAction`, () => {
// TODO: Implement test
@@ -1386,4 +2063,61 @@ suite('ExtensionsActions Test', () => {
return { firstPage: objects, total: objects.length, pageSize: objects.length, getPage: () => null! };
}
function aSingleRemoteExtensionManagementServerService(instantiationService: TestInstantiationService, remoteExtensionManagementService?: IExtensionManagementService): IExtensionManagementServerService {
const remoteExtensionManagementServer: IExtensionManagementServer = {
authority: 'vscode-remote',
label: 'remote',
extensionManagementService: remoteExtensionManagementService || createExtensionManagementService()
};
return {
_serviceBrand: {},
localExtensionManagementServer: null,
remoteExtensionManagementServer,
getExtensionManagementServer: (location: URI) => {
if (location.scheme === REMOTE_HOST_SCHEME) {
return remoteExtensionManagementServer;
}
return null;
}
};
}
function aMultiExtensionManagementServerService(instantiationService: TestInstantiationService, localExtensionManagementService?: IExtensionManagementService, remoteExtensionManagementService?: IExtensionManagementService): IExtensionManagementServerService {
const localExtensionManagementServer: IExtensionManagementServer = {
authority: 'vscode-local',
label: 'local',
extensionManagementService: localExtensionManagementService || createExtensionManagementService()
};
const remoteExtensionManagementServer: IExtensionManagementServer = {
authority: 'vscode-remote',
label: 'remote',
extensionManagementService: remoteExtensionManagementService || createExtensionManagementService()
};
return {
_serviceBrand: {},
localExtensionManagementServer,
remoteExtensionManagementServer,
getExtensionManagementServer: (location: URI) => {
if (location.scheme === Schemas.file) {
return localExtensionManagementServer;
}
if (location.scheme === REMOTE_HOST_SCHEME) {
return remoteExtensionManagementServer;
}
return null;
}
};
}
function createExtensionManagementService(installed: ILocalExtension[] = []): IExtensionManagementService {
return <IExtensionManagementService>{
onInstallExtension: Event.None,
onDidInstallExtension: Event.None,
onUninstallExtension: Event.None,
onDidUninstallExtension: Event.None,
getInstalled: () => Promise.resolve<ILocalExtension[]>(installed),
installFromGallery: (extension: IGalleryExtension) => Promise.reject(new Error('not supported'))
};
}
});

View File

@@ -112,7 +112,7 @@ suite('ExtensionsWorkbenchServiceTest', () => {
test('test gallery extension', async () => {
const expected = aGalleryExtension('expectedName', {
displayName: 'expectedDisplayName',
version: '1.5',
version: '1.5.0',
publisherId: 'expectedPublisherId',
publisher: 'expectedPublisher',
publisherDisplayName: 'expectedPublisherDisplayName',
@@ -140,14 +140,14 @@ suite('ExtensionsWorkbenchServiceTest', () => {
assert.equal(1, pagedResponse.firstPage.length);
const actual = pagedResponse.firstPage[0];
assert.equal(null, actual.type);
assert.equal(ExtensionType.User, actual.type);
assert.equal('expectedName', actual.name);
assert.equal('expectedDisplayName', actual.displayName);
assert.equal('expectedpublisher.expectedname', actual.identifier.id);
assert.equal('expectedPublisher', actual.publisher);
assert.equal('expectedPublisherDisplayName', actual.publisherDisplayName);
assert.equal('1.5', actual.version);
assert.equal('1.5', actual.latestVersion);
assert.equal('1.5.0', actual.version);
assert.equal('1.5.0', actual.latestVersion);
assert.equal('expectedDescription', actual.description);
assert.equal('uri:icon', actual.iconUrl);
assert.equal('fallback:icon', actual.iconUrlFallback);

View File

@@ -5,7 +5,7 @@
import { Event, Emitter } from 'vs/base/common/event';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IExplorerService, IEditableData, IFilesConfiguration, SortOrder, SortOrderConfiguration } from 'vs/workbench/contrib/files/common/files';
import { ExplorerItem, ExplorerModel } from 'vs/workbench/contrib/files/common/explorerModel';
import { URI } from 'vs/base/common/uri';
@@ -36,7 +36,7 @@ export class ExplorerService implements IExplorerService {
private _onDidChangeEditable = new Emitter<ExplorerItem>();
private _onDidSelectResource = new Emitter<{ resource?: URI, reveal?: boolean }>();
private _onDidCopyItems = new Emitter<{ items: ExplorerItem[], cut: boolean, previouslyCutItems: ExplorerItem[] | undefined }>();
private disposables: IDisposable[] = [];
private readonly disposables = new DisposableStore();
private editable: { stat: ExplorerItem, data: IEditableData } | undefined;
private _sortOrder: SortOrder;
private cutItems: ExplorerItem[] | undefined;
@@ -88,18 +88,18 @@ export class ExplorerService implements IExplorerService {
(root?: URI) => getFileEventsExcludes(this.configurationService, root),
(event: IConfigurationChangeEvent) => event.affectsConfiguration(FILES_EXCLUDE_CONFIG)
);
this.disposables.push(fileEventsFilter);
this.disposables.add(fileEventsFilter);
return fileEventsFilter;
}
@memoize get model(): ExplorerModel {
const model = new ExplorerModel(this.contextService);
this.disposables.push(model);
this.disposables.push(this.fileService.onAfterOperation(e => this.onFileOperation(e)));
this.disposables.push(this.fileService.onFileChanges(e => this.onFileChanges(e)));
this.disposables.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue<IFilesConfiguration>())));
this.disposables.push(this.fileService.onDidChangeFileSystemProviderRegistrations(e => {
this.disposables.add(model);
this.disposables.add(this.fileService.onAfterOperation(e => this.onFileOperation(e)));
this.disposables.add(this.fileService.onFileChanges(e => this.onFileChanges(e)));
this.disposables.add(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue<IFilesConfiguration>())));
this.disposables.add(this.fileService.onDidChangeFileSystemProviderRegistrations(e => {
if (e.added && this.fileSystemProviderSchemes.has(e.scheme)) {
// A file system provider got re-registered, we should update all file stats since they might change (got read-only)
this.model.roots.forEach(r => r.forgetChildren());
@@ -108,7 +108,7 @@ export class ExplorerService implements IExplorerService {
this.fileSystemProviderSchemes.add(e.scheme);
}
}));
this.disposables.push(model.onDidChangeRoots(() => this._onDidChangeRoots.fire()));
this.disposables.add(model.onDidChangeRoots(() => this._onDidChangeRoots.fire()));
return model;
}
@@ -380,6 +380,6 @@ export class ExplorerService implements IExplorerService {
}
dispose(): void {
dispose(this.disposables);
this.disposables.dispose();
}
}

View File

@@ -11,7 +11,6 @@ import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { TogglePanelAction } from 'vs/workbench/browser/panel';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
@@ -67,11 +66,9 @@ export class ToggleOrSetOutputScrollLockAction extends Action {
public static readonly ID = 'workbench.output.action.toggleOutputScrollLock';
public static readonly LABEL = nls.localize({ key: 'toggleOutputScrollLock', comment: ['Turn on / off automatic output scrolling'] }, "Toggle Output Scroll Lock");
private toDispose: IDisposable[] = [];
constructor(id: string, label: string, @IOutputService private readonly outputService: IOutputService) {
super(id, label, 'output-action output-scroll-unlock');
this.toDispose.push(this.outputService.onActiveOutputChannel(channel => {
this._register(this.outputService.onActiveOutputChannel(channel => {
const activeChannel = this.outputService.getActiveChannel();
if (activeChannel) {
this.setClassAndLabel(activeChannel.scrollLock);
@@ -104,11 +101,6 @@ export class ToggleOrSetOutputScrollLockAction extends Action {
this.label = nls.localize('outputScrollOff', "Turn Auto Scrolling Off");
}
}
public dispose() {
super.dispose();
this.toDispose = dispose(this.toDispose);
}
}
export class SwitchOutputAction extends Action {
@@ -188,15 +180,13 @@ export class OpenLogOutputFile extends Action {
public static readonly ID = 'workbench.output.action.openLogOutputFile';
public static readonly LABEL = nls.localize('openInLogViewer', "Open Log File");
private disposables: IDisposable[] = [];
constructor(
@IOutputService private readonly outputService: IOutputService,
@IEditorService private readonly editorService: IEditorService,
@IInstantiationService private readonly instantiationService: IInstantiationService
) {
super(OpenLogOutputFile.ID, OpenLogOutputFile.LABEL, 'output-action open-log-file');
this.outputService.onActiveOutputChannel(this.update, this, this.disposables);
this._register(this.outputService.onActiveOutputChannel(this.update, this));
this.update();
}

View File

@@ -1151,6 +1151,10 @@ export class SearchView extends ViewletPanel {
}
onQueryChanged(preserveFocus?: boolean): void {
if (!this.searchWidget.searchInput.inputBox.isInputValid()) {
return;
}
const isRegex = this.searchWidget.searchInput.getRegex();
const isWholeWords = this.searchWidget.searchInput.getWholeWords();
const isCaseSensitive = this.searchWidget.searchInput.getCaseSensitive();

View File

@@ -3,25 +3,18 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import * as crypto from 'crypto';
import { onUnexpectedError } from 'vs/base/common/errors';
import { URI } from 'vs/base/common/uri';
import { IFileService, IFileStat, IResolveFileResult } from 'vs/platform/files/common/files';
import { IFileService, IFileStat } from 'vs/platform/files/common/files';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWindowConfiguration, IWindowService } from 'vs/platform/windows/common/windows';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { endsWith } from 'vs/base/common/strings';
import { Schemas } from 'vs/base/common/network';
import { INotificationService, Severity, IPromptChoice } from 'vs/platform/notification/common/notification';
import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { joinPath } from 'vs/base/common/resources';
import { ITextFileService, ITextFileContent } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileService, } from 'vs/workbench/services/textfile/common/textfiles';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService';
const SshProtocolMatcher = /^([^@:]+@)?([^:]+):/;
const SshUrlMatcher = /^([^@:]+@)?([^:]+):(.+)$/;
@@ -43,60 +36,6 @@ const SecondLevelDomainWhitelist = [
'rhcloud.com',
'google.com'
];
const ModulesToLookFor = [
// Packages that suggest a node server
'express',
'sails',
'koa',
'hapi',
'socket.io',
'restify',
// JS frameworks
'react',
'react-native',
'rnpm-plugin-windows',
'@angular/core',
'@ionic',
'vue',
'tns-core-modules',
// Other interesting packages
'aws-sdk',
'aws-amplify',
'azure',
'azure-storage',
'firebase',
'@google-cloud/common',
'heroku-cli'
];
const PyModulesToLookFor = [
'azure',
'azure-storage-common',
'azure-storage-blob',
'azure-storage-file',
'azure-storage-queue',
'azure-shell',
'azure-cosmos',
'azure-devtools',
'azure-elasticluster',
'azure-eventgrid',
'azure-functions',
'azure-graphrbac',
'azure-keyvault',
'azure-loganalytics',
'azure-monitor',
'azure-servicebus',
'azure-servicefabric',
'azure-storage',
'azure-translator',
'azure-iothub-device-client',
'adal',
'pydocumentdb',
'botbuilder-core',
'botbuilder-schema',
'botframework-connector'
];
type Tags = { [index: string]: boolean | number | string | undefined };
function stripLowLevelDomains(domain: string): string | null {
const match = domain.match(SecondLevelDomainMatcher);
@@ -212,21 +151,14 @@ export function getHashedRemotesFromUri(workspaceUri: URI, fileService: IFileSer
export class WorkspaceStats implements IWorkbenchContribution {
static TAGS: Tags;
private static DISABLE_WORKSPACE_PROMPT_KEY = 'workspaces.dontPromptToOpen';
constructor(
@IFileService private readonly fileService: IFileService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IWindowService private readonly windowService: IWindowService,
@INotificationService private readonly notificationService: INotificationService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IStorageService private readonly storageService: IStorageService,
@ITextFileService private readonly textFileService: ITextFileService,
@ISharedProcessService private readonly sharedProcessService: ISharedProcessService
@ISharedProcessService private readonly sharedProcessService: ISharedProcessService,
@IWorkspaceStatsService private readonly workspaceStatsService: IWorkspaceStatsService
) {
this.report();
}
@@ -234,7 +166,7 @@ export class WorkspaceStats implements IWorkbenchContribution {
private report(): void {
// Workspace Stats
this.resolveWorkspaceTags(this.environmentService.configuration, rootFiles => this.handleWorkspaceFiles(rootFiles))
this.workspaceStatsService.getTags()
.then(tags => this.reportWorkspaceTags(tags), error => onUnexpectedError(error));
// Cloud Stats
@@ -246,372 +178,7 @@ export class WorkspaceStats implements IWorkbenchContribution {
diagnosticsChannel.call('reportWorkspaceStats', this.contextService.getWorkspace());
}
private static searchArray(arr: string[], regEx: RegExp): boolean | undefined {
return arr.some(v => v.search(regEx) > -1) || undefined;
}
/* __GDPR__FRAGMENT__
"WorkspaceTags" : {
"workbench.filesToOpenOrCreate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workbench.filesToDiff" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"workspace.roots" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.empty" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.grunt" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.gulp" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.jake" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.tsconfig" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.jsconfig" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.config.xml" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.vsc.extension" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.asp<NUMBER>" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.sln" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.unity" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.express" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.sails" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.koa" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.hapi" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.socket.io" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.restify" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.rnpm-plugin-windows" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.react" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.@angular/core" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.vue" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.aws-sdk" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.aws-amplify-sdk" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.azure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.azure-storage" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.@google-cloud/common" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.firebase" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.heroku-cli" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.bower" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.yeoman.code.ext" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.cordova.high" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.cordova.low" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.xamarin.android" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.xamarin.ios" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.android.cpp" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.reactNative" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.ionic" : { "classification" : "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": "true" },
"workspace.nativeScript" : { "classification" : "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": "true" },
"workspace.java.pom" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.requirements" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.requirements.star" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.Pipfile" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.conda" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.any-azure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-storage-common" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-storage-blob" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-storage-file" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-storage-queue" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-mgmt" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-shell" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.pulumi-azure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-cosmos" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-devtools" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-elasticluster" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-eventgrid" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-functions" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-graphrbac" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-keyvault" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-loganalytics" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-monitor" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-servicebus" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-servicefabric" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-storage" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-translator" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-iothub-device-client" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-ml" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-cognitiveservices" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.adal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.pydocumentdb" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.botbuilder-core" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.botbuilder-schema" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.botframework-connector" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
}
*/
private resolveWorkspaceTags(configuration: IWindowConfiguration, participant?: (rootFiles: string[]) => void): Promise<Tags> {
const tags: Tags = Object.create(null);
const state = this.contextService.getWorkbenchState();
const workspace = this.contextService.getWorkspace();
function createHash(uri: URI): string {
return crypto.createHash('sha1').update(uri.scheme === Schemas.file ? uri.fsPath : uri.toString()).digest('hex');
}
let workspaceId: string | undefined;
switch (state) {
case WorkbenchState.EMPTY:
workspaceId = undefined;
break;
case WorkbenchState.FOLDER:
workspaceId = createHash(workspace.folders[0].uri);
break;
case WorkbenchState.WORKSPACE:
if (workspace.configuration) {
workspaceId = createHash(workspace.configuration);
}
}
tags['workspace.id'] = workspaceId;
const { filesToOpenOrCreate, filesToDiff } = configuration;
tags['workbench.filesToOpenOrCreate'] = filesToOpenOrCreate && filesToOpenOrCreate.length || 0;
tags['workbench.filesToDiff'] = filesToDiff && filesToDiff.length || 0;
const isEmpty = state === WorkbenchState.EMPTY;
tags['workspace.roots'] = isEmpty ? 0 : workspace.folders.length;
tags['workspace.empty'] = isEmpty;
const folders = !isEmpty ? workspace.folders.map(folder => folder.uri) : this.environmentService.appQuality !== 'stable' && this.findFolders(configuration);
if (!folders || !folders.length || !this.fileService) {
return Promise.resolve(tags);
}
return this.fileService.resolveAll(folders.map(resource => ({ resource }))).then((files: IResolveFileResult[]) => {
const names = (<IFileStat[]>[]).concat(...files.map(result => result.success ? (result.stat!.children || []) : [])).map(c => c.name);
const nameSet = names.reduce((s, n) => s.add(n.toLowerCase()), new Set());
if (participant) {
participant(names);
}
tags['workspace.grunt'] = nameSet.has('gruntfile.js');
tags['workspace.gulp'] = nameSet.has('gulpfile.js');
tags['workspace.jake'] = nameSet.has('jakefile.js');
tags['workspace.tsconfig'] = nameSet.has('tsconfig.json');
tags['workspace.jsconfig'] = nameSet.has('jsconfig.json');
tags['workspace.config.xml'] = nameSet.has('config.xml');
tags['workspace.vsc.extension'] = nameSet.has('vsc-extension-quickstart.md');
tags['workspace.ASP5'] = nameSet.has('project.json') && WorkspaceStats.searchArray(names, /^.+\.cs$/i);
tags['workspace.sln'] = WorkspaceStats.searchArray(names, /^.+\.sln$|^.+\.csproj$/i);
tags['workspace.unity'] = nameSet.has('assets') && nameSet.has('library') && nameSet.has('projectsettings');
tags['workspace.npm'] = nameSet.has('package.json') || nameSet.has('node_modules');
tags['workspace.bower'] = nameSet.has('bower.json') || nameSet.has('bower_components');
tags['workspace.java.pom'] = nameSet.has('pom.xml');
tags['workspace.yeoman.code.ext'] = nameSet.has('vsc-extension-quickstart.md');
tags['workspace.py.requirements'] = nameSet.has('requirements.txt');
tags['workspace.py.requirements.star'] = WorkspaceStats.searchArray(names, /^(.*)requirements(.*)\.txt$/i);
tags['workspace.py.Pipfile'] = nameSet.has('pipfile');
tags['workspace.py.conda'] = WorkspaceStats.searchArray(names, /^environment(\.yml$|\.yaml$)/i);
const mainActivity = nameSet.has('mainactivity.cs') || nameSet.has('mainactivity.fs');
const appDelegate = nameSet.has('appdelegate.cs') || nameSet.has('appdelegate.fs');
const androidManifest = nameSet.has('androidmanifest.xml');
const platforms = nameSet.has('platforms');
const plugins = nameSet.has('plugins');
const www = nameSet.has('www');
const properties = nameSet.has('properties');
const resources = nameSet.has('resources');
const jni = nameSet.has('jni');
if (tags['workspace.config.xml'] &&
!tags['workspace.language.cs'] && !tags['workspace.language.vb'] && !tags['workspace.language.aspx']) {
if (platforms && plugins && www) {
tags['workspace.cordova.high'] = true;
} else {
tags['workspace.cordova.low'] = true;
}
}
if (tags['workspace.config.xml'] &&
!tags['workspace.language.cs'] && !tags['workspace.language.vb'] && !tags['workspace.language.aspx']) {
if (nameSet.has('ionic.config.json')) {
tags['workspace.ionic'] = true;
}
}
if (mainActivity && properties && resources) {
tags['workspace.xamarin.android'] = true;
}
if (appDelegate && resources) {
tags['workspace.xamarin.ios'] = true;
}
if (androidManifest && jni) {
tags['workspace.android.cpp'] = true;
}
function getFilePromises(filename: string, fileService: IFileService, textFileService: ITextFileService, contentHandler: (content: ITextFileContent) => void): Promise<void>[] {
return !nameSet.has(filename) ? [] : (folders as URI[]).map(workspaceUri => {
const uri = workspaceUri.with({ path: `${workspaceUri.path !== '/' ? workspaceUri.path : ''}/${filename}` });
return fileService.exists(uri).then(exists => {
if (!exists) {
return undefined;
}
return textFileService.read(uri, { acceptTextOnly: true }).then(contentHandler);
}, err => {
// Ignore missing file
});
});
}
function addPythonTags(packageName: string): void {
if (PyModulesToLookFor.indexOf(packageName) > -1) {
tags['workspace.py.' + packageName] = true;
}
// cognitive services has a lot of tiny packages. e.g. 'azure-cognitiveservices-search-autosuggest'
if (packageName.indexOf('azure-cognitiveservices') > -1) {
tags['workspace.py.azure-cognitiveservices'] = true;
}
if (packageName.indexOf('azure-mgmt') > -1) {
tags['workspace.py.azure-mgmt'] = true;
}
if (packageName.indexOf('azure-ml') > -1) {
tags['workspace.py.azure-ml'] = true;
}
if (!tags['workspace.py.any-azure']) {
tags['workspace.py.any-azure'] = /azure/i.test(packageName);
}
}
const requirementsTxtPromises = getFilePromises('requirements.txt', this.fileService, this.textFileService, content => {
const dependencies: string[] = content.value.split(/\r\n|\r|\n/);
for (let dependency of dependencies) {
// Dependencies in requirements.txt can have 3 formats: `foo==3.1, foo>=3.1, foo`
const format1 = dependency.split('==');
const format2 = dependency.split('>=');
const packageName = (format1.length === 2 ? format1[0] : format2[0]).trim();
addPythonTags(packageName);
}
});
const pipfilePromises = getFilePromises('pipfile', this.fileService, this.textFileService, content => {
let dependencies: string[] = content.value.split(/\r\n|\r|\n/);
// We're only interested in the '[packages]' section of the Pipfile
dependencies = dependencies.slice(dependencies.indexOf('[packages]') + 1);
for (let dependency of dependencies) {
if (dependency.trim().indexOf('[') > -1) {
break;
}
// All dependencies in Pipfiles follow the format: `<package> = <version, or git repo, or something else>`
if (dependency.indexOf('=') === -1) {
continue;
}
const packageName = dependency.split('=')[0].trim();
addPythonTags(packageName);
}
});
const packageJsonPromises = getFilePromises('package.json', this.fileService, this.textFileService, content => {
try {
const packageJsonContents = JSON.parse(content.value);
let dependencies = packageJsonContents['dependencies'];
let devDependencies = packageJsonContents['devDependencies'];
for (let module of ModulesToLookFor) {
if ('react-native' === module) {
if ((dependencies && dependencies[module]) || (devDependencies && devDependencies[module])) {
tags['workspace.reactNative'] = true;
}
} else if ('tns-core-modules' === module) {
if ((dependencies && dependencies[module]) || (devDependencies && devDependencies[module])) {
tags['workspace.nativescript'] = true;
}
} else {
if ((dependencies && dependencies[module]) || (devDependencies && devDependencies[module])) {
tags['workspace.npm.' + module] = true;
}
}
}
}
catch (e) {
// Ignore errors when resolving file or parsing file contents
}
});
return Promise.all([...packageJsonPromises, ...requirementsTxtPromises, ...pipfilePromises]).then(() => tags);
});
}
private handleWorkspaceFiles(rootFiles: string[]): void {
const state = this.contextService.getWorkbenchState();
const workspace = this.contextService.getWorkspace();
// Handle top-level workspace files for local single folder workspace
if (state === WorkbenchState.FOLDER) {
const workspaceFiles = rootFiles.filter(hasWorkspaceFileExtension);
if (workspaceFiles.length > 0) {
this.doHandleWorkspaceFiles(workspace.folders[0].uri, workspaceFiles);
}
}
}
private doHandleWorkspaceFiles(folder: URI, workspaces: string[]): void {
if (this.storageService.getBoolean(WorkspaceStats.DISABLE_WORKSPACE_PROMPT_KEY, StorageScope.WORKSPACE)) {
return; // prompt disabled by user
}
const doNotShowAgain: IPromptChoice = {
label: localize('never again', "Don't Show Again"),
isSecondary: true,
run: () => this.storageService.store(WorkspaceStats.DISABLE_WORKSPACE_PROMPT_KEY, true, StorageScope.WORKSPACE)
};
// Prompt to open one workspace
if (workspaces.length === 1) {
const workspaceFile = workspaces[0];
this.notificationService.prompt(Severity.Info, localize('workspaceFound', "This folder contains a workspace file '{0}'. Do you want to open it? [Learn more]({1}) about workspace files.", workspaceFile, 'https://go.microsoft.com/fwlink/?linkid=2025315'), [{
label: localize('openWorkspace', "Open Workspace"),
run: () => this.windowService.openWindow([{ workspaceUri: joinPath(folder, workspaceFile) }])
}, doNotShowAgain]);
}
// Prompt to select a workspace from many
else if (workspaces.length > 1) {
this.notificationService.prompt(Severity.Info, localize('workspacesFound', "This folder contains multiple workspace files. Do you want to open one? [Learn more]({0}) about workspace files.", 'https://go.microsoft.com/fwlink/?linkid=2025315'), [{
label: localize('selectWorkspace', "Select Workspace"),
run: () => {
this.quickInputService.pick(
workspaces.map(workspace => ({ label: workspace } as IQuickPickItem)),
{ placeHolder: localize('selectToOpen', "Select a workspace to open") }).then(pick => {
if (pick) {
this.windowService.openWindow([{ workspaceUri: joinPath(folder, pick.label) }]);
}
});
}
}, doNotShowAgain]);
}
}
private findFolders(configuration: IWindowConfiguration): URI[] | undefined {
const folder = this.findFolder(configuration);
return folder && [folder];
}
private findFolder({ filesToOpenOrCreate, filesToDiff }: IWindowConfiguration): URI | undefined {
if (filesToOpenOrCreate && filesToOpenOrCreate.length) {
return this.parentURI(filesToOpenOrCreate[0].fileUri);
} else if (filesToDiff && filesToDiff.length) {
return this.parentURI(filesToDiff[0].fileUri);
}
return undefined;
}
private parentURI(uri: URI | undefined): URI | undefined {
if (!uri) {
return undefined;
}
const path = uri.path;
const i = path.lastIndexOf('/');
return i !== -1 ? uri.with({ path: path.substr(0, i) }) : undefined;
}
private reportWorkspaceTags(tags: Tags): void {
/* __GDPR__
@@ -622,7 +189,6 @@ export class WorkspaceStats implements IWorkbenchContribution {
}
*/
this.telemetryService.publicLog('workspce.tags', tags);
WorkspaceStats.TAGS = tags;
}
private reportRemoteDomains(workspaceUris: URI[]): void {
@@ -689,6 +255,10 @@ export class WorkspaceStats implements IWorkbenchContribution {
});
}
private static searchArray(arr: string[], regEx: RegExp): boolean | undefined {
return arr.some(v => v.search(regEx) > -1) || undefined;
}
/* __GDPR__FRAGMENT__
"AzureTags" : {
"java" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }

View File

@@ -0,0 +1,477 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as crypto from 'crypto';
import { IFileService, IResolveFileResult, IFileStat } from 'vs/platform/files/common/files';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWindowService, IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { INotificationService, IPromptChoice } from 'vs/platform/notification/common/notification';
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ITextFileService, ITextFileContent } from 'vs/workbench/services/textfile/common/textfiles';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
import { localize } from 'vs/nls';
import Severity from 'vs/base/common/severity';
import { joinPath } from 'vs/base/common/resources';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export type Tags = { [index: string]: boolean | number | string | undefined };
const DISABLE_WORKSPACE_PROMPT_KEY = 'workspaces.dontPromptToOpen';
const ModulesToLookFor = [
// Packages that suggest a node server
'express',
'sails',
'koa',
'hapi',
'socket.io',
'restify',
// JS frameworks
'react',
'react-native',
'rnpm-plugin-windows',
'@angular/core',
'@ionic',
'vue',
'tns-core-modules',
// Other interesting packages
'aws-sdk',
'aws-amplify',
'azure',
'azure-storage',
'firebase',
'@google-cloud/common',
'heroku-cli'
];
const PyModulesToLookFor = [
'azure',
'azure-storage-common',
'azure-storage-blob',
'azure-storage-file',
'azure-storage-queue',
'azure-shell',
'azure-cosmos',
'azure-devtools',
'azure-elasticluster',
'azure-eventgrid',
'azure-functions',
'azure-graphrbac',
'azure-keyvault',
'azure-loganalytics',
'azure-monitor',
'azure-servicebus',
'azure-servicefabric',
'azure-storage',
'azure-translator',
'azure-iothub-device-client',
'adal',
'pydocumentdb',
'botbuilder-core',
'botbuilder-schema',
'botframework-connector'
];
export const IWorkspaceStatsService = createDecorator<IWorkspaceStatsService>('workspaceStatsService');
export interface IWorkspaceStatsService {
_serviceBrand: any;
getTags(): Promise<Tags>;
}
export class WorkspaceStatsService implements IWorkspaceStatsService {
_serviceBrand: any;
private _tags: Tags;
constructor(
@IFileService private readonly fileService: IFileService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IWindowService private readonly windowService: IWindowService,
@INotificationService private readonly notificationService: INotificationService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IStorageService private readonly storageService: IStorageService,
@ITextFileService private readonly textFileService: ITextFileService
) { }
public async getTags(): Promise<Tags> {
if (!this._tags) {
this._tags = await this.resolveWorkspaceTags(this.environmentService.configuration, rootFiles => this.handleWorkspaceFiles(rootFiles));
}
return this._tags;
}
/* __GDPR__FRAGMENT__
"WorkspaceTags" : {
"workbench.filesToOpenOrCreate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workbench.filesToDiff" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"workspace.roots" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.empty" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.grunt" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.gulp" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.jake" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.tsconfig" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.jsconfig" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.config.xml" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.vsc.extension" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.asp<NUMBER>" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.sln" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.unity" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.express" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.sails" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.koa" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.hapi" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.socket.io" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.restify" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.rnpm-plugin-windows" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.react" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.@angular/core" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.vue" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.aws-sdk" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.aws-amplify-sdk" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.azure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.azure-storage" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.@google-cloud/common" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.firebase" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.npm.heroku-cli" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.bower" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.yeoman.code.ext" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.cordova.high" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.cordova.low" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.xamarin.android" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.xamarin.ios" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.android.cpp" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.reactNative" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.ionic" : { "classification" : "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": "true" },
"workspace.nativeScript" : { "classification" : "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": "true" },
"workspace.java.pom" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.requirements" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.requirements.star" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.Pipfile" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.conda" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.any-azure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-storage-common" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-storage-blob" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-storage-file" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-storage-queue" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-mgmt" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-shell" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.pulumi-azure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-cosmos" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-devtools" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-elasticluster" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-eventgrid" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-functions" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-graphrbac" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-keyvault" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-loganalytics" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-monitor" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-servicebus" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-servicefabric" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-storage" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-translator" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-iothub-device-client" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-ml" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.azure-cognitiveservices" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.adal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.pydocumentdb" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.botbuilder-core" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.botbuilder-schema" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.py.botframework-connector" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
}
*/
private resolveWorkspaceTags(configuration: IWindowConfiguration, participant?: (rootFiles: string[]) => void): Promise<Tags> {
const tags: Tags = Object.create(null);
const state = this.contextService.getWorkbenchState();
const workspace = this.contextService.getWorkspace();
function createHash(uri: URI): string {
return crypto.createHash('sha1').update(uri.scheme === Schemas.file ? uri.fsPath : uri.toString()).digest('hex');
}
let workspaceId: string | undefined;
switch (state) {
case WorkbenchState.EMPTY:
workspaceId = undefined;
break;
case WorkbenchState.FOLDER:
workspaceId = createHash(workspace.folders[0].uri);
break;
case WorkbenchState.WORKSPACE:
if (workspace.configuration) {
workspaceId = createHash(workspace.configuration);
}
}
tags['workspace.id'] = workspaceId;
const { filesToOpenOrCreate, filesToDiff } = configuration;
tags['workbench.filesToOpenOrCreate'] = filesToOpenOrCreate && filesToOpenOrCreate.length || 0;
tags['workbench.filesToDiff'] = filesToDiff && filesToDiff.length || 0;
const isEmpty = state === WorkbenchState.EMPTY;
tags['workspace.roots'] = isEmpty ? 0 : workspace.folders.length;
tags['workspace.empty'] = isEmpty;
const folders = !isEmpty ? workspace.folders.map(folder => folder.uri) : this.environmentService.appQuality !== 'stable' && this.findFolders(configuration);
if (!folders || !folders.length || !this.fileService) {
return Promise.resolve(tags);
}
return this.fileService.resolveAll(folders.map(resource => ({ resource }))).then((files: IResolveFileResult[]) => {
const names = (<IFileStat[]>[]).concat(...files.map(result => result.success ? (result.stat!.children || []) : [])).map(c => c.name);
const nameSet = names.reduce((s, n) => s.add(n.toLowerCase()), new Set());
if (participant) {
participant(names);
}
tags['workspace.grunt'] = nameSet.has('gruntfile.js');
tags['workspace.gulp'] = nameSet.has('gulpfile.js');
tags['workspace.jake'] = nameSet.has('jakefile.js');
tags['workspace.tsconfig'] = nameSet.has('tsconfig.json');
tags['workspace.jsconfig'] = nameSet.has('jsconfig.json');
tags['workspace.config.xml'] = nameSet.has('config.xml');
tags['workspace.vsc.extension'] = nameSet.has('vsc-extension-quickstart.md');
tags['workspace.ASP5'] = nameSet.has('project.json') && this.searchArray(names, /^.+\.cs$/i);
tags['workspace.sln'] = this.searchArray(names, /^.+\.sln$|^.+\.csproj$/i);
tags['workspace.unity'] = nameSet.has('assets') && nameSet.has('library') && nameSet.has('projectsettings');
tags['workspace.npm'] = nameSet.has('package.json') || nameSet.has('node_modules');
tags['workspace.bower'] = nameSet.has('bower.json') || nameSet.has('bower_components');
tags['workspace.java.pom'] = nameSet.has('pom.xml');
tags['workspace.yeoman.code.ext'] = nameSet.has('vsc-extension-quickstart.md');
tags['workspace.py.requirements'] = nameSet.has('requirements.txt');
tags['workspace.py.requirements.star'] = this.searchArray(names, /^(.*)requirements(.*)\.txt$/i);
tags['workspace.py.Pipfile'] = nameSet.has('pipfile');
tags['workspace.py.conda'] = this.searchArray(names, /^environment(\.yml$|\.yaml$)/i);
const mainActivity = nameSet.has('mainactivity.cs') || nameSet.has('mainactivity.fs');
const appDelegate = nameSet.has('appdelegate.cs') || nameSet.has('appdelegate.fs');
const androidManifest = nameSet.has('androidmanifest.xml');
const platforms = nameSet.has('platforms');
const plugins = nameSet.has('plugins');
const www = nameSet.has('www');
const properties = nameSet.has('properties');
const resources = nameSet.has('resources');
const jni = nameSet.has('jni');
if (tags['workspace.config.xml'] &&
!tags['workspace.language.cs'] && !tags['workspace.language.vb'] && !tags['workspace.language.aspx']) {
if (platforms && plugins && www) {
tags['workspace.cordova.high'] = true;
} else {
tags['workspace.cordova.low'] = true;
}
}
if (tags['workspace.config.xml'] &&
!tags['workspace.language.cs'] && !tags['workspace.language.vb'] && !tags['workspace.language.aspx']) {
if (nameSet.has('ionic.config.json')) {
tags['workspace.ionic'] = true;
}
}
if (mainActivity && properties && resources) {
tags['workspace.xamarin.android'] = true;
}
if (appDelegate && resources) {
tags['workspace.xamarin.ios'] = true;
}
if (androidManifest && jni) {
tags['workspace.android.cpp'] = true;
}
function getFilePromises(filename: string, fileService: IFileService, textFileService: ITextFileService, contentHandler: (content: ITextFileContent) => void): Promise<void>[] {
return !nameSet.has(filename) ? [] : (folders as URI[]).map(workspaceUri => {
const uri = workspaceUri.with({ path: `${workspaceUri.path !== '/' ? workspaceUri.path : ''}/${filename}` });
return fileService.exists(uri).then(exists => {
if (!exists) {
return undefined;
}
return textFileService.read(uri, { acceptTextOnly: true }).then(contentHandler);
}, err => {
// Ignore missing file
});
});
}
function addPythonTags(packageName: string): void {
if (PyModulesToLookFor.indexOf(packageName) > -1) {
tags['workspace.py.' + packageName] = true;
}
// cognitive services has a lot of tiny packages. e.g. 'azure-cognitiveservices-search-autosuggest'
if (packageName.indexOf('azure-cognitiveservices') > -1) {
tags['workspace.py.azure-cognitiveservices'] = true;
}
if (packageName.indexOf('azure-mgmt') > -1) {
tags['workspace.py.azure-mgmt'] = true;
}
if (packageName.indexOf('azure-ml') > -1) {
tags['workspace.py.azure-ml'] = true;
}
if (!tags['workspace.py.any-azure']) {
tags['workspace.py.any-azure'] = /azure/i.test(packageName);
}
}
const requirementsTxtPromises = getFilePromises('requirements.txt', this.fileService, this.textFileService, content => {
const dependencies: string[] = content.value.split(/\r\n|\r|\n/);
for (let dependency of dependencies) {
// Dependencies in requirements.txt can have 3 formats: `foo==3.1, foo>=3.1, foo`
const format1 = dependency.split('==');
const format2 = dependency.split('>=');
const packageName = (format1.length === 2 ? format1[0] : format2[0]).trim();
addPythonTags(packageName);
}
});
const pipfilePromises = getFilePromises('pipfile', this.fileService, this.textFileService, content => {
let dependencies: string[] = content.value.split(/\r\n|\r|\n/);
// We're only interested in the '[packages]' section of the Pipfile
dependencies = dependencies.slice(dependencies.indexOf('[packages]') + 1);
for (let dependency of dependencies) {
if (dependency.trim().indexOf('[') > -1) {
break;
}
// All dependencies in Pipfiles follow the format: `<package> = <version, or git repo, or something else>`
if (dependency.indexOf('=') === -1) {
continue;
}
const packageName = dependency.split('=')[0].trim();
addPythonTags(packageName);
}
});
const packageJsonPromises = getFilePromises('package.json', this.fileService, this.textFileService, content => {
try {
const packageJsonContents = JSON.parse(content.value);
let dependencies = packageJsonContents['dependencies'];
let devDependencies = packageJsonContents['devDependencies'];
for (let module of ModulesToLookFor) {
if ('react-native' === module) {
if ((dependencies && dependencies[module]) || (devDependencies && devDependencies[module])) {
tags['workspace.reactNative'] = true;
}
} else if ('tns-core-modules' === module) {
if ((dependencies && dependencies[module]) || (devDependencies && devDependencies[module])) {
tags['workspace.nativescript'] = true;
}
} else {
if ((dependencies && dependencies[module]) || (devDependencies && devDependencies[module])) {
tags['workspace.npm.' + module] = true;
}
}
}
}
catch (e) {
// Ignore errors when resolving file or parsing file contents
}
});
return Promise.all([...packageJsonPromises, ...requirementsTxtPromises, ...pipfilePromises]).then(() => tags);
});
}
private handleWorkspaceFiles(rootFiles: string[]): void {
const state = this.contextService.getWorkbenchState();
const workspace = this.contextService.getWorkspace();
// Handle top-level workspace files for local single folder workspace
if (state === WorkbenchState.FOLDER) {
const workspaceFiles = rootFiles.filter(hasWorkspaceFileExtension);
if (workspaceFiles.length > 0) {
this.doHandleWorkspaceFiles(workspace.folders[0].uri, workspaceFiles);
}
}
}
private doHandleWorkspaceFiles(folder: URI, workspaces: string[]): void {
if (this.storageService.getBoolean(DISABLE_WORKSPACE_PROMPT_KEY, StorageScope.WORKSPACE)) {
return; // prompt disabled by user
}
const doNotShowAgain: IPromptChoice = {
label: localize('never again', "Don't Show Again"),
isSecondary: true,
run: () => this.storageService.store(DISABLE_WORKSPACE_PROMPT_KEY, true, StorageScope.WORKSPACE)
};
// Prompt to open one workspace
if (workspaces.length === 1) {
const workspaceFile = workspaces[0];
this.notificationService.prompt(Severity.Info, localize('workspaceFound', "This folder contains a workspace file '{0}'. Do you want to open it? [Learn more]({1}) about workspace files.", workspaceFile, 'https://go.microsoft.com/fwlink/?linkid=2025315'), [{
label: localize('openWorkspace', "Open Workspace"),
run: () => this.windowService.openWindow([{ workspaceUri: joinPath(folder, workspaceFile) }])
}, doNotShowAgain]);
}
// Prompt to select a workspace from many
else if (workspaces.length > 1) {
this.notificationService.prompt(Severity.Info, localize('workspacesFound', "This folder contains multiple workspace files. Do you want to open one? [Learn more]({0}) about workspace files.", 'https://go.microsoft.com/fwlink/?linkid=2025315'), [{
label: localize('selectWorkspace', "Select Workspace"),
run: () => {
this.quickInputService.pick(
workspaces.map(workspace => ({ label: workspace } as IQuickPickItem)),
{ placeHolder: localize('selectToOpen', "Select a workspace to open") }).then(pick => {
if (pick) {
this.windowService.openWindow([{ workspaceUri: joinPath(folder, pick.label) }]);
}
});
}
}, doNotShowAgain]);
}
}
private findFolders(configuration: IWindowConfiguration): URI[] | undefined {
const folder = this.findFolder(configuration);
return folder && [folder];
}
private findFolder({ filesToOpenOrCreate, filesToDiff }: IWindowConfiguration): URI | undefined {
if (filesToOpenOrCreate && filesToOpenOrCreate.length) {
return this.parentURI(filesToOpenOrCreate[0].fileUri);
} else if (filesToDiff && filesToDiff.length) {
return this.parentURI(filesToDiff[0].fileUri);
}
return undefined;
}
private parentURI(uri: URI | undefined): URI | undefined {
if (!uri) {
return undefined;
}
const path = uri.path;
const i = path.lastIndexOf('/');
return i !== -1 ? uri.with({ path: path.substr(0, i) }) : undefined;
}
private searchArray(arr: string[], regEx: RegExp): boolean | undefined {
return arr.some(v => v.search(regEx) > -1) || undefined;
}
}

View File

@@ -9,6 +9,7 @@ import { IEditorModel } from 'vs/platform/editor/common/editor';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { EditorInput, EditorModel, GroupIdentifier, IEditorInput } from 'vs/workbench/common/editor';
import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/common/webview';
import { UnownedDisposable as Unowned } from 'vs/base/common/lifecycle';
class WebviewIconsManager {
private readonly _icons = new Map<string, { light: URI, dark: URI }>();
@@ -58,6 +59,7 @@ export class WebviewEditorInput extends EditorInput {
private _name: string;
private _iconPath?: { light: URI, dark: URI };
private _group?: GroupIdentifier;
private readonly _webview: WebviewEditorOverlay;
constructor(
public readonly id: string,
@@ -67,14 +69,14 @@ export class WebviewEditorInput extends EditorInput {
readonly location: URI;
readonly id: ExtensionIdentifier;
},
public readonly webview: WebviewEditorOverlay,
webview: Unowned<WebviewEditorOverlay>,
) {
super();
this._name = name;
this.extension = extension;
this._register(webview); // The input owns this webview
this._webview = this._register(webview.acquire()); // The input owns this webview
}
public getTypeId(): string {
@@ -105,6 +107,10 @@ export class WebviewEditorInput extends EditorInput {
this._onDidChangeLabel.fire();
}
public get webview() {
return this._webview;
}
public get iconPath() {
return this._iconPath;
}
@@ -147,7 +153,7 @@ export class RevivedWebviewEditorInput extends WebviewEditorInput {
readonly id: ExtensionIdentifier
},
private readonly reviver: (input: WebviewEditorInput) => Promise<void>,
webview: WebviewEditorOverlay,
webview: Unowned<WebviewEditorOverlay>,
) {
super(id, viewType, name, extension, webview);
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { equals } from 'vs/base/common/arrays';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, toDisposable, UnownedDisposable } from 'vs/base/common/lifecycle';
import { values } from 'vs/base/common/map';
import { URI } from 'vs/base/common/uri';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
@@ -144,7 +144,7 @@ export class WebviewEditorService implements IWebviewEditorService {
): WebviewEditorInput {
const webview = this.createWebiew(id, extension, options);
const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, id, viewType, title, extension, webview);
const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, id, viewType, title, extension, new UnownedDisposable(webview));
this._editorService.openEditor(webviewInput, { pinned: true, preserveFocus: showOptions.preserveFocus }, showOptions.group);
return webviewInput;
}
@@ -191,7 +191,7 @@ export class WebviewEditorService implements IWebviewEditorService {
const promise = new Promise<void>(r => { resolve = r; });
this._revivalPool.add(webview, resolve!);
return promise;
}, webview);
}, new UnownedDisposable(webview));
webviewInput.iconPath = iconPath;

View File

@@ -237,6 +237,13 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe
}
return getFilePath();
case 'relativeFileDirname':
let dirname = paths.dirname(getFilePath());
if (folderUri) {
return paths.normalize(paths.relative(getFolderUri().fsPath, dirname));
}
return dirname;
case 'fileDirname':
return paths.dirname(getFilePath());

View File

@@ -221,7 +221,7 @@ export class FileDialogService implements IFileDialogService {
const schema = this.getFileSystemSchema(options);
if (this.shouldUseSimplified(schema)) {
if (!options.availableFileSystems) {
options.availableFileSystems = [schema]; // by default only allow saving in the own file system
options.availableFileSystems = this.ensureFileSchema(schema); // always allow file as well
}
return this.saveRemoteResource(options);
@@ -239,7 +239,7 @@ export class FileDialogService implements IFileDialogService {
const schema = this.getFileSystemSchema(options);
if (this.shouldUseSimplified(schema)) {
if (!options.availableFileSystems) {
options.availableFileSystems = [schema]; // by default only allow loading in the own file system
options.availableFileSystems = this.ensureFileSchema(schema); // always allow file as well
}
const uri = await this.pickRemoteResource(options);

View File

@@ -14,7 +14,7 @@ import { ExtHostCustomersRegistry } from 'vs/workbench/api/common/extHostCustome
import { ExtHostContext, ExtHostExtensionServiceShape, IExtHostContext, MainContext } from 'vs/workbench/api/common/extHost.protocol';
import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import { IRPCProtocolLogger, RPCProtocol, RequestInitiator, ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol';
import { ResolvedAuthority, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { RemoteAuthorityResolverError, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import * as nls from 'vs/nls';
import { Action } from 'vs/base/common/actions';
@@ -249,15 +249,17 @@ export class ExtensionHostProcessManager extends Disposable {
return this._extensionHostProcessWorker && Boolean(this._extensionHostProcessWorker.getInspectPort());
}
public async resolveAuthority(remoteAuthority: string): Promise<ResolvedAuthority> {
public async resolveAuthority(remoteAuthority: string): Promise<ResolverResult> {
const authorityPlusIndex = remoteAuthority.indexOf('+');
if (authorityPlusIndex === -1) {
// This authority does not need to be resolved, simply parse the port number
const pieces = remoteAuthority.split(':');
return Promise.resolve({
authority: remoteAuthority,
host: pieces[0],
port: parseInt(pieces[1], 10)
authority: {
authority: remoteAuthority,
host: pieces[0],
port: parseInt(pieces[1], 10)
}
});
}
const proxy = await this._getExtensionHostProcessProxy();

View File

@@ -76,19 +76,20 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH
webSocketFactory: this._webSocketFactory,
addressProvider: {
getAddress: async () => {
const { host, port } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority);
return { host, port };
const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority);
return { host: authority.host, port: authority.port };
}
},
signService: this._signService
};
return this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority).then((resolvedAuthority) => {
return this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority).then((resolverResult) => {
const startParams: IRemoteExtensionHostStartParams = {
language: platform.language,
debugId: this._environmentService.debugExtensionHost.debugId,
break: this._environmentService.debugExtensionHost.break,
port: this._environmentService.debugExtensionHost.port,
env: resolverResult.options && resolverResult.options.extensionHostEnv
};
const extDevLocs = this._environmentService.extensionDevelopmentLocationURI;

View File

@@ -19,7 +19,7 @@ import { IExtensionEnablementService } from 'vs/workbench/services/extensionMana
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInitDataProvider, RemoteExtensionHostClient } from 'vs/workbench/services/extensions/common/remoteExtensionHostClient';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAuthorityResolverService, ResolvedAuthority, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -424,8 +424,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
const extensionHost = this._extensionHostProcessManagers[0];
this._remoteAuthorityResolverService.clearResolvedAuthority(remoteAuthority);
try {
const resolvedAuthority = await extensionHost.resolveAuthority(remoteAuthority);
this._remoteAuthorityResolverService.setResolvedAuthority(resolvedAuthority);
const result = await extensionHost.resolveAuthority(remoteAuthority);
this._remoteAuthorityResolverService.setResolvedAuthority(result.authority, result.options);
} catch (err) {
this._remoteAuthorityResolverService.setResolvedAuthorityError(remoteAuthority, err);
}
@@ -446,7 +446,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
localExtensions = localExtensions.filter(extension => this._isEnabled(extension));
if (remoteAuthority) {
let resolvedAuthority: ResolvedAuthority;
let resolvedAuthority: ResolverResult;
try {
resolvedAuthority = await extensionHost.resolveAuthority(remoteAuthority);
@@ -468,7 +468,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
}
// set the resolved authority
this._remoteAuthorityResolverService.setResolvedAuthority(resolvedAuthority);
this._remoteAuthorityResolverService.setResolvedAuthority(resolvedAuthority.authority, resolvedAuthority.options);
// monitor for breakage
const connection = this._remoteAgentService.getConnection();

View File

@@ -12,33 +12,39 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { TestStorageService } from 'vs/workbench/test/workbenchTestServices';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
class TestKeyboardMapperFactory extends BrowserKeyboardMapperFactoryBase {
constructor(notificationService: INotificationService, storageService: IStorageService, commandService: ICommandService) {
super(notificationService, storageService, commandService);
let keymapInfos: IKeymapInfo[] = KeyboardLayoutContribution.INSTANCE.layoutInfos;
const keymapInfos: IKeymapInfo[] = KeyboardLayoutContribution.INSTANCE.layoutInfos;
this._keymapInfos.push(...keymapInfos.map(info => (new KeymapInfo(info.layout, info.secondaryLayouts, info.mapping, info.isUserKeyboardLayout))));
this._mru = this._keymapInfos;
this._initialized = true;
this.onKeyboardLayoutChanged();
const usLayout = this.getUSStandardLayout();
if (usLayout) {
this.setActiveKeyMapping(usLayout.mapping);
}
}
}
suite('keyboard layout loader', () => {
let instantiationService: TestInstantiationService = new TestInstantiationService();
let notitifcationService = instantiationService.stub(INotificationService, {});
let storageService = instantiationService.stub(IStorageService, {});
let notitifcationService = instantiationService.stub(INotificationService, new TestNotificationService());
let storageService = instantiationService.stub(IStorageService, new TestStorageService());
let commandService = instantiationService.stub(ICommandService, {});
let instance = new TestKeyboardMapperFactory(notitifcationService, storageService, commandService);
test.skip('load default US keyboard layout', () => {
test('load default US keyboard layout', () => {
assert.notEqual(instance.activeKeyboardLayout, null);
assert.equal(instance.activeKeyboardLayout!.isUSStandard, true);
});
test.skip('isKeyMappingActive', () => {
test('isKeyMappingActive', () => {
assert.equal(instance.isKeyMappingActive({
KeyA: {
value: 'a',

View File

@@ -121,8 +121,8 @@ export class RemoteAgentConnection extends Disposable implements IRemoteAgentCon
} else {
this._onReconnecting.fire(undefined);
}
const { host, port } = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority);
return { host, port };
const { authority } = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority);
return { host: authority.host, port: authority.port };
}
},
signService: this._signService

View File

@@ -105,8 +105,8 @@ export class TunnelService implements ITunnelService {
webSocketFactory: nodeWebSocketFactory,
addressProvider: {
getAddress: async () => {
const { host, port } = await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority);
return { host, port };
const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority);
return { host: authority.host, port: authority.port };
}
},
signService: this.signService

View File

@@ -168,6 +168,7 @@ registerSingleton(IMenubarService, MenubarService);
registerSingleton(IURLService, RelayURLService);
registerSingleton(ITunnelService, TunnelService, true);
registerSingleton(ICredentialsService, KeytarCredentialsService, true);
registerSingleton(IWorkspaceStatsService, WorkspaceStatsService, true);
//#endregion
@@ -456,6 +457,7 @@ import 'vs/workbench/contrib/experiments/electron-browser/experiments.contributi
// Issues
import 'vs/workbench/contrib/issue/electron-browser/issue.contribution';
import { IWorkspaceStatsService, WorkspaceStatsService } from 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService';
// {{SQL CARBON EDIT}}
// SQL