Initial VS Code 1.19 source merge (#571)

* Initial 1.19 xcopy

* Fix yarn build

* Fix numerous build breaks

* Next batch of build break fixes

* More build break fixes

* Runtime breaks

* Additional post merge fixes

* Fix windows setup file

* Fix test failures.

* Update license header blocks to refer to source eula
This commit is contained in:
Karl Burtram
2018-01-28 23:37:17 -08:00
committed by GitHub
parent 9a1ac20710
commit 251ae01c3e
8009 changed files with 93378 additions and 35634 deletions

View File

@@ -21,13 +21,13 @@ export class Menu implements IMenu {
private _onDidChange = new Emitter<IMenu>();
constructor(
private _id: MenuId,
id: MenuId,
startupSignal: TPromise<boolean>,
@ICommandService private _commandService: ICommandService,
@IContextKeyService private _contextKeyService: IContextKeyService
) {
startupSignal.then(_ => {
const menuItems = MenuRegistry.getMenuItems(_id);
const menuItems = MenuRegistry.getMenuItems(id);
const keysFilter = new Set<string>();
let group: MenuItemGroup;
@@ -47,15 +47,9 @@ export class Menu implements IMenu {
}
// subscribe to context changes
this._disposables.push(this._contextKeyService.onDidChangeContext(keys => {
if (!keys) {
return;
}
for (let k of keys) {
if (keysFilter.has(k)) {
this._onDidChange.fire();
return;
}
this._disposables.push(this._contextKeyService.onDidChangeContext(event => {
if (event.affectsSome(keysFilter)) {
this._onDidChange.fire();
}
}));

View File

@@ -23,6 +23,6 @@ export class MenuService implements IMenuService {
}
createMenu(id: MenuId, contextKeyService: IContextKeyService): IMenu {
return new Menu(id, this._extensionService.onReady(), this._commandService, contextKeyService);
return new Menu(id, this._extensionService.whenInstalledExtensionsRegistered(), this._commandService, contextKeyService);
}
}

View File

@@ -12,18 +12,27 @@ import { NullCommandService } from 'vs/platform/commands/common/commands';
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
import { IExtensionPoint } from 'vs/platform/extensions/common/extensionsRegistry';
import { TPromise } from 'vs/base/common/winjs.base';
import { ExtensionPointContribution, IExtensionDescription, IExtensionsStatus, IExtensionService, ActivationTimes } from 'vs/platform/extensions/common/extensions';
import { ExtensionPointContribution, IExtensionDescription, IExtensionsStatus, IExtensionService, ProfileSession } from 'vs/platform/extensions/common/extensions';
import Event, { Emitter } from 'vs/base/common/event';
// --- service instances
class MockExtensionService implements IExtensionService {
public _serviceBrand: any;
private _onDidRegisterExtensions = new Emitter<IExtensionDescription[]>();
public get onDidRegisterExtensions(): Event<IExtensionDescription[]> {
return this._onDidRegisterExtensions.event;
}
onDidChangeExtensionsStatus = null;
public activateByEvent(activationEvent: string): TPromise<void> {
throw new Error('Not implemented');
}
public onReady(): TPromise<boolean> {
public whenInstalledExtensionsRegistered(): TPromise<boolean> {
return TPromise.as(true);
}
@@ -39,7 +48,7 @@ class MockExtensionService implements IExtensionService {
throw new Error('Not implemented');
}
public getExtensionsActivationTimes(): { [id: string]: ActivationTimes; } {
public startExtensionHostProfile(): TPromise<ProfileSession> {
throw new Error('Not implemented');
}
@@ -54,6 +63,10 @@ class MockExtensionService implements IExtensionService {
public stopExtensionHost(): void {
throw new Error('Method not implemented.');
}
public getExtensionHostInformation(): any {
throw new Error('Method not implemented.');
}
}
const extensionService = new MockExtensionService();

View File

@@ -14,7 +14,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IFilesConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files';
import { ILogService } from 'vs/platform/log/common/log';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspacesMainService, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
export class BackupMainService implements IBackupMainService {
@@ -28,8 +28,7 @@ export class BackupMainService implements IBackupMainService {
constructor(
@IEnvironmentService environmentService: IEnvironmentService,
@IConfigurationService private configurationService: IConfigurationService,
@ILogService private logService: ILogService,
@IWorkspacesMainService private workspacesService: IWorkspacesMainService
@ILogService private logService: ILogService
) {
this.backupHome = environmentService.backupHome;
this.workspacesJsonPath = environmentService.backupWorkspacesPath;
@@ -66,7 +65,7 @@ export class BackupMainService implements IBackupMainService {
}
private getHotExitConfig(): string {
const config = this.configurationService.getConfiguration<IFilesConfiguration>();
const config = this.configurationService.getValue<IFilesConfiguration>();
return (config && config.files && config.files.hotExit) || HotExitConfiguration.ON_EXIT;
}
@@ -310,7 +309,7 @@ export class BackupMainService implements IBackupMainService {
fs.mkdirSync(this.backupHome);
}
fs.writeFileSync(this.workspacesJsonPath, JSON.stringify(this.backups));
extfs.writeFileAndFlushSync(this.workspacesJsonPath, JSON.stringify(this.backups));
} catch (ex) {
this.logService.error(`Backup: Could not save workspaces.json: ${ex.toString()}`);
}

View File

@@ -19,10 +19,9 @@ import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainSe
import { IBackupWorkspacesFormat } from 'vs/platform/backup/common/backup';
import { HotExitConfiguration } from 'vs/platform/files/common/files';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { LogMainService } from 'vs/platform/log/common/log';
import { ConsoleLogMainService } from 'vs/platform/log/common/log';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { createHash } from 'crypto';
import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { getRandomTestPath } from 'vs/workbench/test/workbenchTestServices';
suite('BackupMainService', () => {
@@ -31,12 +30,11 @@ suite('BackupMainService', () => {
const backupWorkspacesPath = path.join(backupHome, 'workspaces.json');
const environmentService = new EnvironmentService(parseArgs(process.argv), process.execPath);
const logService = new LogMainService(environmentService);
class TestBackupMainService extends BackupMainService {
constructor(backupHome: string, backupWorkspacesPath: string, configService: TestConfigurationService) {
super(environmentService, configService, new LogMainService(environmentService), new WorkspacesMainService(environmentService, logService));
super(environmentService, configService, new ConsoleLogMainService(environmentService));
this.backupHome = backupHome;
this.workspacesJsonPath = backupWorkspacesPath;

View File

@@ -17,4 +17,19 @@ export interface IClipboardService {
* Writes text to the system clipboard.
*/
writeText(text: string): void;
}
/**
* Reads the content of the clipboard in plain text
*/
readText(): string;
/**
* Reads text from the system find pasteboard.
*/
readFindText(): string;
/**
* Writes text to the system find pasteboard.
*/
writeFindText(text: string): void;
}

View File

@@ -7,6 +7,7 @@
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { clipboard } from 'electron';
import * as platform from 'vs/base/common/platform';
export class ClipboardService implements IClipboardService {
@@ -15,4 +16,21 @@ export class ClipboardService implements IClipboardService {
public writeText(text: string): void {
clipboard.writeText(text);
}
}
public readText(): string {
return clipboard.readText();
}
public readFindText(): string {
if (platform.isMacintosh) {
return clipboard.readFindText();
}
return '';
}
public writeFindText(text: string): void {
if (platform.isMacintosh) {
clipboard.writeFindText(text);
}
}
}

View File

@@ -6,11 +6,12 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ICommandService, ICommand, ICommandEvent, CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ICommandService, ICommandEvent, CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import Event, { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ILogService } from 'vs/platform/log/common/log';
export class CommandService extends Disposable implements ICommandService {
@@ -24,26 +25,31 @@ export class CommandService extends Disposable implements ICommandService {
constructor(
@IInstantiationService private _instantiationService: IInstantiationService,
@IExtensionService private _extensionService: IExtensionService,
@IContextKeyService private _contextKeyService: IContextKeyService
@IContextKeyService private _contextKeyService: IContextKeyService,
@ILogService private _logService: ILogService
) {
super();
this._extensionService.onReady().then(value => this._extensionHostIsReady = value);
this._extensionService.whenInstalledExtensionsRegistered().then(value => this._extensionHostIsReady = value);
}
executeCommand<T>(id: string, ...args: any[]): TPromise<T> {
this._logService.trace('CommandService#executeCommand', id);
// we always send an activation event, but
// we don't wait for it when the extension
// host didn't yet start
// host didn't yet start and the command is already registered
const activation = this._extensionService.activateByEvent(`onCommand:${id}`);
return this._extensionHostIsReady
? activation.then(_ => this._tryExecuteCommand(id, args))
: this._tryExecuteCommand(id, args);
if (!this._extensionHostIsReady && CommandsRegistry.getCommand(id)) {
return this._tryExecuteCommand(id, args);
} else {
return activation.then(_ => this._tryExecuteCommand(id, args));
}
}
private _tryExecuteCommand(id: string, args: any[]): TPromise<any> {
const command = this._getCommand(id);
const command = CommandsRegistry.getCommand(id);
if (!command) {
return TPromise.wrapError(new Error(`command '${id}' not found`));
}
@@ -61,8 +67,4 @@ export class CommandService extends Disposable implements ICommandService {
return TPromise.wrapError(err);
}
}
private _getCommand(id: string): ICommand {
return CommandsRegistry.getCommand(id);
}
}

View File

@@ -9,19 +9,26 @@ import { IDisposable } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { CommandService } from 'vs/platform/commands/common/commandService';
import { IExtensionService, ExtensionPointContribution, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IExtensionService, ExtensionPointContribution, IExtensionDescription, IExtensionHostInformation, ProfileSession } from 'vs/platform/extensions/common/extensions';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { IExtensionPoint } from 'vs/platform/extensions/common/extensionsRegistry';
import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService';
import { SimpleConfigurationService } from 'vs/editor/standalone/browser/simpleServices';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import Event, { Emitter } from 'vs/base/common/event';
import { NoopLogService } from 'vs/platform/log/common/log';
class SimpleExtensionService implements IExtensionService {
_serviceBrand: any;
activateByEvent(activationEvent: string): TPromise<void> {
return this.onReady().then(() => { });
private _onDidRegisterExtensions = new Emitter<IExtensionDescription[]>();
get onDidRegisterExtensions(): Event<IExtensionDescription[]> {
return this._onDidRegisterExtensions.event;
}
onReady(): TPromise<boolean> {
onDidChangeExtensionsStatus = null;
activateByEvent(activationEvent: string): TPromise<void> {
return this.whenInstalledExtensionsRegistered().then(() => { });
}
whenInstalledExtensionsRegistered(): TPromise<boolean> {
return TPromise.as(true);
}
readExtensionPointContributions<T>(extPoint: IExtensionPoint<T>): TPromise<ExtensionPointContribution<T>[]> {
@@ -30,12 +37,15 @@ class SimpleExtensionService implements IExtensionService {
getExtensionsStatus() {
return undefined;
}
getExtensionsActivationTimes() {
getExtensionHostInformation(): IExtensionHostInformation {
return undefined;
}
getExtensions(): TPromise<IExtensionDescription[]> {
return TPromise.wrap([]);
}
startExtensionHostProfile(): TPromise<ProfileSession> {
throw new Error('Not implemented');
}
restartExtensionHost(): void {
}
startExtensionHost(): void {
@@ -65,7 +75,7 @@ suite('CommandService', function () {
lastEvent = activationEvent;
return super.activateByEvent(activationEvent);
}
}, new ContextKeyService(new SimpleConfigurationService()));
}, new ContextKeyService(new SimpleConfigurationService()), new NoopLogService());
return service.executeCommand('foo').then(() => {
assert.ok(lastEvent, 'onCommand:foo');
@@ -83,7 +93,7 @@ suite('CommandService', function () {
activateByEvent(activationEvent: string): TPromise<void> {
return TPromise.wrapError<void>(new Error('bad_activate'));
}
}, new ContextKeyService(new SimpleConfigurationService()));
}, new ContextKeyService(new SimpleConfigurationService()), new NoopLogService());
return service.executeCommand('foo').then(() => assert.ok(false), err => {
assert.equal(err.message, 'bad_activate');
@@ -95,14 +105,36 @@ suite('CommandService', function () {
let callCounter = 0;
let reg = CommandsRegistry.registerCommand('bar', () => callCounter += 1);
let resolve: Function;
let service = new CommandService(new InstantiationService(), new class extends SimpleExtensionService {
onReady() {
return new TPromise<boolean>(_resolve => { resolve = _resolve; });
whenInstalledExtensionsRegistered() {
return new TPromise<boolean>(_resolve => { /*ignore*/ });
}
}, new ContextKeyService(new SimpleConfigurationService()));
}, new ContextKeyService(new SimpleConfigurationService()), new NoopLogService());
return service.executeCommand('bar').then(() => {
service.executeCommand('bar');
assert.equal(callCounter, 1);
reg.dispose();
});
test('issue #34913: !onReady, unknown command', function () {
let callCounter = 0;
let resolveFunc: Function;
// let reg = CommandsRegistry.registerCommand('bar', () => callCounter += 1);
let service = new CommandService(new InstantiationService(), new class extends SimpleExtensionService {
whenInstalledExtensionsRegistered() {
return new TPromise<boolean>(_resolve => { resolveFunc = _resolve; });
}
}, new ContextKeyService(new SimpleConfigurationService()), new NoopLogService());
let r = service.executeCommand('bar');
assert.equal(callCounter, 0);
let reg = CommandsRegistry.registerCommand('bar', () => callCounter += 1);
resolveFunc(true);
return r.then(() => {
reg.dispose();
assert.equal(callCounter, 1);
});
@@ -113,7 +145,8 @@ suite('CommandService', function () {
let commandService = new CommandService(
new InstantiationService(),
new SimpleExtensionService(),
contextKeyService
contextKeyService,
new NoopLogService()
);
let counter = 0;

View File

@@ -11,7 +11,7 @@ import Event from 'vs/base/common/event';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry';
import { StrictResourceMap } from 'vs/base/common/map';
export const IConfigurationService = createDecorator<IConfigurationService>('configurationService');
@@ -57,12 +57,18 @@ export interface IConfigurationService {
getConfigurationData(): IConfigurationData;
getConfiguration<T>(): T;
getConfiguration<T>(section: string): T;
getConfiguration<T>(overrides: IConfigurationOverrides): T;
getConfiguration<T>(section: string, overrides: IConfigurationOverrides): T;
getValue<T>(key: string, overrides?: IConfigurationOverrides): T;
/**
* Fetches the value of the section for the given overrides.
* Value can be of native type or an object keyed off the section name.
*
* @param section - Section of the configuraion. Can be `null` or `undefined`.
* @param overrides - Overrides that has to be applied while fetching
*
*/
getValue<T>(): T;
getValue<T>(section: string): T;
getValue<T>(overrides: IConfigurationOverrides): T;
getValue<T>(section: string, overrides: IConfigurationOverrides): T;
updateValue(key: string, value: any): TPromise<void>;
updateValue(key: string, value: any, overrides: IConfigurationOverrides): TPromise<void>;
@@ -72,7 +78,7 @@ export interface IConfigurationService {
reloadConfiguration(): TPromise<void>;
reloadConfiguration(folder: IWorkspaceFolder): TPromise<void>;
inspect<T>(key: string): {
inspect<T>(key: string, overrides?: IConfigurationOverrides): {
default: T,
user: T,
workspace: T,
@@ -124,6 +130,26 @@ export function compare(from: IConfigurationModel, to: IConfigurationModel): { a
return { added, removed, updated };
}
export function toOverrides(raw: any, conflictReporter: (message: string) => void): IOverrides[] {
const overrides: IOverrides[] = [];
const configurationProperties = Registry.as<IConfigurationRegistry>(Extensions.Configuration).getConfigurationProperties();
for (const key of Object.keys(raw)) {
if (OVERRIDE_PROPERTY_PATTERN.test(key)) {
const overrideRaw = {};
for (const keyInOverrideRaw in raw[key]) {
if (configurationProperties[keyInOverrideRaw] && configurationProperties[keyInOverrideRaw].overridable) {
overrideRaw[keyInOverrideRaw] = raw[key][keyInOverrideRaw];
}
}
overrides.push({
identifiers: [overrideIdentifierFromKey(key).trim()],
contents: toValuesTree(overrideRaw, conflictReporter)
});
}
}
return overrides;
}
export function toValuesTree(properties: { [qualifiedKey: string]: any }, conflictReporter: (message: string) => void): any {
const root = Object.create(null);
@@ -153,7 +179,7 @@ export function addToValueTree(settingsTreeRoot: any, key: string, value: any, c
return;
}
curr = obj;
};
}
if (typeof curr === 'object') {
curr[last] = value; // workaround https://github.com/Microsoft/vscode/issues/13606

View File

@@ -7,74 +7,60 @@
import * as json from 'vs/base/common/json';
import { StrictResourceMap } from 'vs/base/common/map';
import * as arrays from 'vs/base/common/arrays';
import * as types from 'vs/base/common/types';
import * as objects from 'vs/base/common/objects';
import URI from 'vs/base/common/uri';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry';
import { IOverrides, overrideIdentifierFromKey, addToValueTree, toValuesTree, IConfigurationModel, merge, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys, IConfigurationChangeEvent, ConfigurationTarget, removeFromValueTree } from 'vs/platform/configuration/common/configuration';
import { OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry';
import { IOverrides, overrideIdentifierFromKey, addToValueTree, toValuesTree, IConfigurationModel, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys, IConfigurationChangeEvent, ConfigurationTarget, removeFromValueTree, toOverrides } from 'vs/platform/configuration/common/configuration';
import { Workspace } from 'vs/platform/workspace/common/workspace';
export class ConfigurationModel implements IConfigurationModel {
constructor(protected _contents: any = {}, protected _keys: string[] = [], protected _overrides: IOverrides[] = []) {
private isFrozen: boolean = false;
constructor(
private _contents: any = {},
private _keys: string[] = [],
private _overrides: IOverrides[] = []
) {
}
public get contents(): any {
return this._contents;
get contents(): any {
return this.checkAndFreeze(this._contents);
}
public get overrides(): IOverrides[] {
return this._overrides;
get overrides(): IOverrides[] {
return this.checkAndFreeze(this._overrides);
}
public get keys(): string[] {
return this._keys;
get keys(): string[] {
return this.checkAndFreeze(this._keys);
}
public getSectionContents<V>(section: string): V {
return this.contents[section];
getValue<V>(section: string): V {
return section ? getConfigurationValue<any>(this.contents, section) : this.contents;
}
public setValue(key: string, value: any) {
this.addKey(key);
addToValueTree(this._contents, key, value, e => { throw new Error(e); });
}
public removeValue(key: string): void {
if (this.removeKey(key)) {
removeFromValueTree(this._contents, key);
}
}
public setValueInOverrides(overrideIdentifier: string, key: string, value: any): void {
let override = this._overrides.filter(override => override.identifiers.indexOf(overrideIdentifier) !== -1)[0];
if (!override) {
override = { identifiers: [overrideIdentifier], contents: {} };
this._overrides.push(override);
}
addToValueTree(override.contents, key, value, e => { throw new Error(e); });
}
public override<V>(identifier: string): ConfigurationModel {
override(identifier: string): ConfigurationModel {
const overrideContents = this.getContentsForOverrideIdentifer(identifier);
if (!overrideContents || typeof overrideContents !== 'object' || !Object.keys(overrideContents).length) {
// If there are no valid overrides, use base contents
return new ConfigurationModel(this._contents);
// If there are no valid overrides, return self
return this;
}
let contents = {};
for (const key of arrays.distinct([...Object.keys(this._contents), ...Object.keys(overrideContents)])) {
for (const key of arrays.distinct([...Object.keys(this.contents), ...Object.keys(overrideContents)])) {
let contentsForKey = this._contents[key];
let contentsForKey = this.contents[key];
let overrideContentsForKey = overrideContents[key];
// If there are override contents for the key, clone and merge otherwise use base contents
if (overrideContentsForKey) {
// Clone and merge only if base contents and override contents are of type object otherwise just override
if (typeof contentsForKey === 'object' && typeof overrideContentsForKey === 'object') {
contentsForKey = objects.clone(contentsForKey);
merge(contentsForKey, overrideContentsForKey, true);
contentsForKey = objects.deepClone(contentsForKey);
this.mergeContents(contentsForKey, overrideContentsForKey);
} else {
contentsForKey = overrideContentsForKey;
}
@@ -82,33 +68,62 @@ export class ConfigurationModel implements IConfigurationModel {
contents[key] = contentsForKey;
}
return new ConfigurationModel(contents);
}
public merge(other: ConfigurationModel, overwrite: boolean = true): ConfigurationModel {
const mergedModel = new ConfigurationModel();
this.doMerge(mergedModel, this, overwrite);
this.doMerge(mergedModel, other, overwrite);
return mergedModel;
}
merge(...others: ConfigurationModel[]): ConfigurationModel {
const contents = objects.deepClone(this.contents);
const overrides = objects.deepClone(this.overrides);
const keys = [...this.keys];
protected doMerge(source: ConfigurationModel, target: ConfigurationModel, overwrite: boolean = true) {
merge(source.contents, objects.clone(target.contents), overwrite);
const overrides = objects.clone(source._overrides);
for (const override of target._overrides) {
const [sourceOverride] = overrides.filter(o => arrays.equals(o.identifiers, override.identifiers));
if (sourceOverride) {
merge(sourceOverride.contents, override.contents, overwrite);
} else {
overrides.push(override);
for (const other of others) {
this.mergeContents(contents, other.contents);
for (const otherOverride of other.overrides) {
const [override] = overrides.filter(o => arrays.equals(o.identifiers, otherOverride.identifiers));
if (override) {
this.mergeContents(override.contents, otherOverride.contents);
} else {
overrides.push(otherOverride);
}
}
for (const key of other.keys) {
if (keys.indexOf(key) === -1) {
keys.push(key);
}
}
}
source._overrides = overrides;
source._keys = arrays.distinct([...source._keys, ...target.keys]);
return new ConfigurationModel(contents, keys, overrides);
}
freeze(): ConfigurationModel {
// {{SQL CARBON EDIT}}
// this.isFrozen = true;
return this;
}
private mergeContents(source: any, target: any): void {
for (const key of Object.keys(target)) {
if (key in source) {
if (types.isObject(source[key]) && types.isObject(target[key])) {
this.mergeContents(source[key], target[key]);
continue;
}
}
source[key] = objects.deepClone(target[key]);
}
}
private checkAndFreeze<T>(data: T): T {
if (this.isFrozen && !Object.isFrozen(data)) {
return objects.deepFreeze(data);
}
return data;
}
private getContentsForOverrideIdentifer(identifier: string): any {
for (const override of this._overrides) {
for (const override of this.overrides) {
if (override.identifiers.indexOf(identifier) !== -1) {
return override.contents;
}
@@ -116,25 +131,6 @@ export class ConfigurationModel implements IConfigurationModel {
return null;
}
private addKey(key: string): void {
let index = this._keys.length;
for (let i = 0; i < index; i++) {
if (key.indexOf(this._keys[i]) === 0) {
index = i;
}
}
this._keys.splice(index, 1, key);
}
private removeKey(key: string): boolean {
let index = this._keys.indexOf(key);
if (index !== -1) {
this._keys.splice(index, 1);
return true;
}
return false;
}
toJSON(): IConfigurationModel {
return {
contents: this.contents,
@@ -142,50 +138,81 @@ export class ConfigurationModel implements IConfigurationModel {
keys: this.keys
};
}
// Update methods
public setValue(key: string, value: any) {
this.addKey(key);
addToValueTree(this.contents, key, value, e => { throw new Error(e); });
}
public removeValue(key: string): void {
if (this.removeKey(key)) {
removeFromValueTree(this.contents, key);
}
}
private addKey(key: string): void {
let index = this.keys.length;
for (let i = 0; i < index; i++) {
if (key.indexOf(this.keys[i]) === 0) {
index = i;
}
}
this.keys.splice(index, 1, key);
}
private removeKey(key: string): boolean {
let index = this.keys.indexOf(key);
if (index !== -1) {
this.keys.splice(index, 1);
return true;
}
return false;
}
}
export class DefaultConfigurationModel extends ConfigurationModel {
constructor() {
super(getDefaultValues());
this._keys = getConfigurationKeys();
this._overrides = Object.keys(this._contents)
.filter(key => OVERRIDE_PROPERTY_PATTERN.test(key))
.map(key => {
return <IOverrides>{
const contents = getDefaultValues();
const keys = getConfigurationKeys();
const overrides: IOverrides[] = [];
for (const key of Object.keys(contents)) {
if (OVERRIDE_PROPERTY_PATTERN.test(key)) {
overrides.push({
identifiers: [overrideIdentifierFromKey(key).trim()],
contents: toValuesTree(this._contents[key], message => console.error(`Conflict in default settings file: ${message}`))
};
});
}
public get keys(): string[] {
return this._keys;
}
}
interface Overrides extends IOverrides {
raw: any;
}
export class CustomConfigurationModel extends ConfigurationModel {
protected _parseErrors: any[] = [];
constructor(content: string = '', private name: string = '') {
super();
if (content) {
this.update(content);
contents: toValuesTree(contents[key], message => console.error(`Conflict in default settings file: ${message}`))
});
}
}
super(contents, keys, overrides);
}
}
export class ConfigurationModelParser {
private _configurationModel: ConfigurationModel = null;
private _parseErrors: any[] = [];
constructor(protected readonly _name: string) { }
get configurationModel(): ConfigurationModel {
return this._configurationModel || new ConfigurationModel();
}
public get errors(): any[] {
get errors(): any[] {
return this._parseErrors;
}
public update(content: string): void {
let parsed: any = {};
let overrides: Overrides[] = [];
public parse(content: string): void {
const raw = this.parseContent(content);
const configurationModel = this.parseRaw(raw);
this._configurationModel = new ConfigurationModel(configurationModel.contents, configurationModel.keys, configurationModel.overrides);
}
protected parseContent(content: string): any {
let raw: any = {};
let currentProperty: string = null;
let currentParent: any = [];
let previousParents: any[] = [];
@@ -197,17 +224,6 @@ export class CustomConfigurationModel extends ConfigurationModel {
} else if (currentProperty) {
currentParent[currentProperty] = value;
}
if (OVERRIDE_PROPERTY_PATTERN.test(currentProperty)) {
onOverrideSettingsValue(currentProperty, value);
}
}
function onOverrideSettingsValue(property: string, value: any): void {
overrides.push({
identifiers: [overrideIdentifierFromKey(property).trim()],
raw: value,
contents: null
});
}
let visitor: json.JSONVisitor = {
@@ -242,88 +258,41 @@ export class CustomConfigurationModel extends ConfigurationModel {
if (content) {
try {
json.visit(content, visitor);
parsed = currentParent[0] || {};
raw = currentParent[0] || {};
} catch (e) {
console.error(`Error while parsing settings file ${this.name}: ${e}`);
console.error(`Error while parsing settings file ${this._name}: ${e}`);
this._parseErrors = [e];
}
}
this.processRaw(parsed);
const configurationProperties = Registry.as<IConfigurationRegistry>(Extensions.Configuration).getConfigurationProperties();
this._overrides = overrides.map<IOverrides>(override => {
// Filter unknown and non-overridable properties
const raw = {};
for (const key in override.raw) {
if (configurationProperties[key] && configurationProperties[key].overridable) {
raw[key] = override.raw[key];
}
}
return {
identifiers: override.identifiers,
contents: toValuesTree(raw, message => console.error(`Conflict in settings file ${this.name}: ${message}`))
};
});
return raw;
}
protected processRaw(raw: any): void {
this._contents = toValuesTree(raw, message => console.error(`Conflict in settings file ${this.name}: ${message}`));
this._keys = Object.keys(raw);
protected parseRaw(raw: any): IConfigurationModel {
const contents = toValuesTree(raw, message => console.error(`Conflict in settings file ${this._name}: ${message}`));
const keys = Object.keys(raw);
const overrides: IOverrides[] = toOverrides(raw, message => console.error(`Conflict in settings file ${this._name}: ${message}`));
return { contents, keys, overrides };
}
}
export class Configuration {
private _globalConfiguration: ConfigurationModel;
private _workspaceConsolidatedConfiguration: ConfigurationModel;
protected _foldersConsolidatedConfigurations: StrictResourceMap<ConfigurationModel>;
private _workspaceConsolidatedConfiguration: ConfigurationModel = null;
private _foldersConsolidatedConfigurations: StrictResourceMap<ConfigurationModel> = new StrictResourceMap<ConfigurationModel>();
constructor(protected _defaults: ConfigurationModel,
protected _user: ConfigurationModel,
protected _workspaceConfiguration: ConfigurationModel = new ConfigurationModel(),
protected folders: StrictResourceMap<ConfigurationModel> = new StrictResourceMap<ConfigurationModel>(),
protected _memoryConfiguration: ConfigurationModel = new ConfigurationModel(),
protected _memoryConfigurationByResource: StrictResourceMap<ConfigurationModel> = new StrictResourceMap<ConfigurationModel>()) {
this.merge();
constructor(
private _defaultConfiguration: ConfigurationModel,
private _userConfiguration: ConfigurationModel,
private _workspaceConfiguration: ConfigurationModel = new ConfigurationModel(),
private _folderConfigurations: StrictResourceMap<ConfigurationModel> = new StrictResourceMap<ConfigurationModel>(),
private _memoryConfiguration: ConfigurationModel = new ConfigurationModel(),
private _memoryConfigurationByResource: StrictResourceMap<ConfigurationModel> = new StrictResourceMap<ConfigurationModel>()) {
}
get defaults(): ConfigurationModel {
return this._defaults;
}
get user(): ConfigurationModel {
return this._user;
}
get workspace(): ConfigurationModel {
return this._workspaceConfiguration;
}
protected merge(): void {
this._globalConfiguration = this._defaults.merge(this._user);
this.updateWorkspaceConsolidateConfiguration();
this._foldersConsolidatedConfigurations = new StrictResourceMap<ConfigurationModel>();
for (const folder of this.folders.keys()) {
this.mergeFolder(folder);
}
}
private updateWorkspaceConsolidateConfiguration() {
this._workspaceConsolidatedConfiguration = this._globalConfiguration.merge(this._workspaceConfiguration).merge(this._memoryConfiguration);
}
protected mergeFolder(folder: URI) {
this._foldersConsolidatedConfigurations.set(folder, this._workspaceConsolidatedConfiguration.merge(this.folders.get(folder)));
}
getSection<C>(section: string = '', overrides: IConfigurationOverrides, workspace: Workspace): C {
const configModel = this.getConsolidateConfigurationModel(overrides, workspace);
return objects.clone(section ? configModel.getSectionContents<C>(section) : configModel.contents);
}
getValue(key: string, overrides: IConfigurationOverrides, workspace: Workspace): any {
getValue(section: string, overrides: IConfigurationOverrides, workspace: Workspace): any {
const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides, workspace);
return objects.clone(getConfigurationValue<any>(consolidateConfigurationModel.contents, key));
return consolidateConfigurationModel.getValue(section);
}
updateValue(key: string, value: any, overrides: IConfigurationOverrides = {}): void {
@@ -345,11 +314,11 @@ export class Configuration {
}
if (!overrides.resource) {
this.updateWorkspaceConsolidateConfiguration();
this._workspaceConsolidatedConfiguration = null;
}
}
lookup<C>(key: string, overrides: IConfigurationOverrides, workspace: Workspace): {
inspect<C>(key: string, overrides: IConfigurationOverrides, workspace: Workspace): {
default: C,
user: C,
workspace: C,
@@ -360,14 +329,14 @@ export class Configuration {
const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides, workspace);
const folderConfigurationModel = this.getFolderConfigurationModelForResource(overrides.resource, workspace);
const memoryConfigurationModel = overrides.resource ? this._memoryConfigurationByResource.get(overrides.resource) || this._memoryConfiguration : this._memoryConfiguration;
return objects.clone({
default: getConfigurationValue<C>(overrides.overrideIdentifier ? this._defaults.override(overrides.overrideIdentifier).contents : this._defaults.contents, key),
user: getConfigurationValue<C>(overrides.overrideIdentifier ? this._user.override(overrides.overrideIdentifier).contents : this._user.contents, key),
workspace: workspace ? getConfigurationValue<C>(overrides.overrideIdentifier ? this._workspaceConfiguration.override(overrides.overrideIdentifier).contents : this._workspaceConfiguration.contents, key) : void 0, //Check on workspace exists or not because _workspaceConfiguration is never null
workspaceFolder: folderConfigurationModel ? getConfigurationValue<C>(overrides.overrideIdentifier ? folderConfigurationModel.override(overrides.overrideIdentifier).contents : folderConfigurationModel.contents, key) : void 0,
memory: getConfigurationValue<C>(overrides.overrideIdentifier ? memoryConfigurationModel.override(overrides.overrideIdentifier).contents : memoryConfigurationModel.contents, key),
value: getConfigurationValue<C>(consolidateConfigurationModel.contents, key)
});
return {
default: overrides.overrideIdentifier ? this._defaultConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this._defaultConfiguration.freeze().getValue(key),
user: overrides.overrideIdentifier ? this._userConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this._userConfiguration.freeze().getValue(key),
workspace: workspace ? overrides.overrideIdentifier ? this._workspaceConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this._workspaceConfiguration.freeze().getValue(key) : void 0, //Check on workspace exists or not because _workspaceConfiguration is never null
workspaceFolder: folderConfigurationModel ? overrides.overrideIdentifier ? folderConfigurationModel.freeze().override(overrides.overrideIdentifier).getValue(key) : folderConfigurationModel.freeze().getValue(key) : void 0,
memory: overrides.overrideIdentifier ? memoryConfigurationModel.freeze().override(overrides.overrideIdentifier).getValue(key) : memoryConfigurationModel.freeze().getValue(key),
value: consolidateConfigurationModel.getValue(key)
};
}
keys(workspace: Workspace): {
@@ -377,76 +346,163 @@ export class Configuration {
workspaceFolder: string[];
} {
const folderConfigurationModel = this.getFolderConfigurationModelForResource(null, workspace);
return objects.clone({
default: this._defaults.keys,
user: this._user.keys,
workspace: this._workspaceConfiguration.keys,
workspaceFolder: folderConfigurationModel ? folderConfigurationModel.keys : []
});
return {
default: this._defaultConfiguration.freeze().keys,
user: this._userConfiguration.freeze().keys,
workspace: this._workspaceConfiguration.freeze().keys,
workspaceFolder: folderConfigurationModel ? folderConfigurationModel.freeze().keys : []
};
}
private getConsolidateConfigurationModel<C>(overrides: IConfigurationOverrides, workspace: Workspace): ConfigurationModel {
updateDefaultConfiguration(defaultConfiguration: ConfigurationModel): void {
this._defaultConfiguration = defaultConfiguration;
this._workspaceConsolidatedConfiguration = null;
this._foldersConsolidatedConfigurations.clear();
}
updateUserConfiguration(userConfiguration: ConfigurationModel): void {
this._userConfiguration = userConfiguration;
this._workspaceConsolidatedConfiguration = null;
this._foldersConsolidatedConfigurations.clear();
}
updateWorkspaceConfiguration(workspaceConfiguration: ConfigurationModel): void {
this._workspaceConfiguration = workspaceConfiguration;
this._workspaceConsolidatedConfiguration = null;
this._foldersConsolidatedConfigurations.clear();
}
updateFolderConfiguration(resource: URI, configuration: ConfigurationModel): void {
this._folderConfigurations.set(resource, configuration);
this._foldersConsolidatedConfigurations.delete(resource);
}
deleteFolderConfiguration(resource: URI): void {
this.folders.delete(resource);
this._foldersConsolidatedConfigurations.delete(resource);
}
get defaults(): ConfigurationModel {
return this._defaultConfiguration;
}
get user(): ConfigurationModel {
return this._userConfiguration;
}
get workspace(): ConfigurationModel {
return this._workspaceConfiguration;
}
protected get folders(): StrictResourceMap<ConfigurationModel> {
return this._folderConfigurations;
}
private get memory(): ConfigurationModel {
return this._memoryConfiguration;
}
private get memoryByResource(): StrictResourceMap<ConfigurationModel> {
return this._memoryConfigurationByResource;
}
private getConsolidateConfigurationModel(overrides: IConfigurationOverrides, workspace: Workspace): ConfigurationModel {
let configurationModel = this.getConsolidatedConfigurationModelForResource(overrides, workspace);
return overrides.overrideIdentifier ? configurationModel.override(overrides.overrideIdentifier) : configurationModel;
}
private getConsolidatedConfigurationModelForResource({ resource }: IConfigurationOverrides, workspace: Workspace): ConfigurationModel {
if (!workspace) {
return this._globalConfiguration;
}
let consolidateConfiguration = this.getWorkspaceConsolidatedConfiguration();
if (!resource) {
return this._workspaceConsolidatedConfiguration;
}
let consolidateConfiguration = this._workspaceConsolidatedConfiguration;
const root = workspace.getFolder(resource);
if (root) {
consolidateConfiguration = this._foldersConsolidatedConfigurations.get(root.uri) || this._workspaceConsolidatedConfiguration;
}
const memoryConfigurationForResource = this._memoryConfigurationByResource.get(resource);
if (memoryConfigurationForResource) {
consolidateConfiguration = consolidateConfiguration.merge(memoryConfigurationForResource);
if (workspace && resource) {
const root = workspace.getFolder(resource);
if (root) {
consolidateConfiguration = this.getFolderConsolidatedConfiguration(root.uri) || consolidateConfiguration;
}
const memoryConfigurationForResource = this._memoryConfigurationByResource.get(resource);
if (memoryConfigurationForResource) {
consolidateConfiguration = consolidateConfiguration.merge(memoryConfigurationForResource);
}
}
return consolidateConfiguration;
}
private getFolderConfigurationModelForResource(resource: URI, workspace: Workspace): ConfigurationModel {
if (!workspace || !resource) {
return null;
private getWorkspaceConsolidatedConfiguration(): ConfigurationModel {
if (!this._workspaceConsolidatedConfiguration) {
this._workspaceConsolidatedConfiguration = this._defaultConfiguration.merge(this._userConfiguration).merge(this._workspaceConfiguration).merge(this._memoryConfiguration).freeze();
}
const root = workspace.getFolder(resource);
return root ? this.folders.get(root.uri) : null;
return this._workspaceConsolidatedConfiguration;
}
public toData(): IConfigurationData {
private getFolderConsolidatedConfiguration(folder: URI): ConfigurationModel {
let folderConsolidatedConfiguration = this._foldersConsolidatedConfigurations.get(folder);
if (!folderConsolidatedConfiguration) {
const workspaceConsolidateConfiguration = this.getWorkspaceConsolidatedConfiguration();
const folderConfiguration = this._folderConfigurations.get(folder);
if (folderConfiguration) {
folderConsolidatedConfiguration = workspaceConsolidateConfiguration.merge(folderConfiguration).freeze();
this._foldersConsolidatedConfigurations.set(folder, folderConsolidatedConfiguration);
} else {
folderConsolidatedConfiguration = workspaceConsolidateConfiguration;
}
}
return folderConsolidatedConfiguration;
}
private getFolderConfigurationModelForResource(resource: URI, workspace: Workspace): ConfigurationModel {
if (workspace && resource) {
const root = workspace.getFolder(resource);
if (root) {
return this._folderConfigurations.get(root.uri);
}
}
return null;
}
toData(): IConfigurationData {
return {
defaults: {
contents: this._defaults.contents,
overrides: this._defaults.overrides,
keys: this._defaults.keys
contents: this._defaultConfiguration.contents,
overrides: this._defaultConfiguration.overrides,
keys: this._defaultConfiguration.keys
},
user: {
contents: this._user.contents,
overrides: this._user.overrides,
keys: this._user.keys
contents: this._userConfiguration.contents,
overrides: this._userConfiguration.overrides,
keys: this._userConfiguration.keys
},
workspace: {
contents: this._workspaceConfiguration.contents,
overrides: this._workspaceConfiguration.overrides,
keys: this._workspaceConfiguration.keys
},
folders: this.folders.keys().reduce((result, folder) => {
const { contents, overrides, keys } = this.folders.get(folder);
folders: this._folderConfigurations.keys().reduce((result, folder) => {
const { contents, overrides, keys } = this._folderConfigurations.get(folder);
result[folder.toString()] = { contents, overrides, keys };
return result;
}, Object.create({}))
};
}
allKeys(workspace: Workspace): string[] {
let keys = this.keys(workspace);
let all = [...keys.default];
const addKeys = (keys) => {
for (const key of keys) {
if (all.indexOf(key) === -1) {
all.push(key);
}
}
};
addKeys(keys.user);
addKeys(keys.workspace);
for (const resource of this.folders.keys()) {
addKeys(this.folders.get(resource).keys);
}
return all;
}
public static parse(data: IConfigurationData): Configuration {
const defaultConfiguration = Configuration.parseConfigurationModel(data.defaults);
const userConfiguration = Configuration.parseConfigurationModel(data.user);
@@ -455,11 +511,11 @@ export class Configuration {
result.set(URI.parse(key), Configuration.parseConfigurationModel(data.folders[key]));
return result;
}, new StrictResourceMap<ConfigurationModel>());
return new Configuration(defaultConfiguration, userConfiguration, workspaceConfiguration, folders, new ConfigurationModel(), new StrictResourceMap<ConfigurationModel>());
return new Configuration(defaultConfiguration, userConfiguration, workspaceConfiguration, folders);
}
private static parseConfigurationModel(model: IConfigurationModel): ConfigurationModel {
return new ConfigurationModel(model.contents, model.keys, model.overrides);
return new ConfigurationModel(model.contents, model.keys, model.overrides).freeze();
}
}
@@ -487,29 +543,6 @@ export class AbstractConfigurationChangeEvent {
}
}
export class AllKeysConfigurationChangeEvent extends AbstractConfigurationChangeEvent implements IConfigurationChangeEvent {
private _changedConfiguration: ConfigurationModel = null;
constructor(readonly affectedKeys: string[], readonly source: ConfigurationTarget, readonly sourceConfig: any) { super(); }
get changedConfiguration(): ConfigurationModel {
if (!this._changedConfiguration) {
this._changedConfiguration = new ConfigurationModel();
this.updateKeys(this._changedConfiguration, this.affectedKeys);
}
return this._changedConfiguration;
}
get changedConfigurationByResource(): StrictResourceMap<IConfigurationModel> {
return new StrictResourceMap();
}
affectsConfiguration(config: string, resource?: URI): boolean {
return this.doesConfigurationContains(this.changedConfiguration, config);
}
}
export class ConfigurationChangeEvent extends AbstractConfigurationChangeEvent implements IConfigurationChangeEvent {
private _source: ConfigurationTarget;
@@ -529,8 +562,8 @@ export class ConfigurationChangeEvent extends AbstractConfigurationChangeEvent i
return this._changedConfigurationByResource;
}
change(event: ConfigurationChangeEvent): ConfigurationChangeEvent
change(keys: string[], resource?: URI): ConfigurationChangeEvent
change(event: ConfigurationChangeEvent): ConfigurationChangeEvent;
change(keys: string[], resource?: URI): ConfigurationChangeEvent;
change(arg1: any, arg2?: any): ConfigurationChangeEvent {
if (arg1 instanceof ConfigurationChangeEvent) {
this._changedConfiguration = this._changedConfiguration.merge(arg1._changedConfiguration);
@@ -554,7 +587,7 @@ export class ConfigurationChangeEvent extends AbstractConfigurationChangeEvent i
get affectedKeys(): string[] {
const keys = [...this._changedConfiguration.keys];
this._changedConfigurationByResource.forEach(model => keys.push(...model.keys));
return keys;
return arrays.distinct(keys);
}
get source(): ConfigurationTarget {
@@ -599,4 +632,4 @@ export class ConfigurationChangeEvent extends AbstractConfigurationChangeEvent i
}
return changedConfigurationByResource;
}
}
}

View File

@@ -11,7 +11,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
import types = require('vs/base/common/types');
import * as strings from 'vs/base/common/strings';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { clone } from 'vs/base/common/objects';
import { deepClone } from 'vs/base/common/objects';
export const Extensions = {
Configuration: 'base.contributions.configuration'
@@ -27,7 +27,7 @@ export interface IConfigurationRegistry {
/**
* Register multiple configurations to the registry.
*/
registerConfigurations(configurations: IConfigurationNode[], validate?: boolean): void;
registerConfigurations(configurations: IConfigurationNode[], defaultConfigurations: IDefaultConfigurationExtension[], validate?: boolean): void;
/**
* Signal that the schema of a configuration setting has changes. It is currently only supported to change enumeration values.
@@ -35,8 +35,6 @@ export interface IConfigurationRegistry {
*/
notifyConfigurationSchemaUpdated(configuration: IConfigurationNode): void;
registerDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void;
/**
* Event that fires whenver a configuration has been
* registered.
@@ -53,6 +51,11 @@ export interface IConfigurationRegistry {
*/
getConfigurationProperties(): { [qualifiedKey: string]: IConfigurationPropertySchema };
/**
* Returns all excluded configurations settings of all configuration nodes contributed to this registry.
*/
getExcludedConfigurationProperties(): { [qualifiedKey: string]: IConfigurationPropertySchema };
/**
* Register the identifiers for editor configurations
*/
@@ -69,6 +72,7 @@ export interface IConfigurationPropertySchema extends IJSONSchema {
isExecutable?: boolean;
scope?: ConfigurationScope;
notMultiRootAdopted?: boolean;
included?: boolean;
}
export interface IConfigurationNode {
@@ -99,6 +103,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
private configurationContributors: IConfigurationNode[];
private configurationProperties: { [qualifiedKey: string]: IJSONSchema };
private excludedConfigurationProperties: { [qualifiedKey: string]: IJSONSchema };
private editorConfigurationSchema: IJSONSchema;
private overrideIdentifiers: string[] = [];
private overridePropertyPattern: string;
@@ -110,16 +115,22 @@ class ConfigurationRegistry implements IConfigurationRegistry {
this.configurationContributors = [];
this.editorConfigurationSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown editor configuration setting' };
this.configurationProperties = {};
this.excludedConfigurationProperties = {};
this.computeOverridePropertyPattern();
contributionRegistry.registerSchema(editorConfigurationSchemaId, this.editorConfigurationSchema);
}
public registerConfiguration(configuration: IConfigurationNode, validate: boolean = true): void {
this.registerConfigurations([configuration], validate);
this.registerConfigurations([configuration], [], validate);
}
public registerConfigurations(configurations: IConfigurationNode[], validate: boolean = true): void {
public registerConfigurations(configurations: IConfigurationNode[], defaultConfigurations: IDefaultConfigurationExtension[], validate: boolean = true): void {
const configurationNode = this.toConfiguration(defaultConfigurations);
if (configurationNode) {
configurations.push(configurationNode);
}
const properties: string[] = [];
configurations.forEach(configuration => {
properties.push(...this.validateAndRegisterProperties(configuration, validate)); // fills in defaults
@@ -140,7 +151,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
this.updateOverridePropertyPatternKey();
}
public registerDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void {
private toConfiguration(defaultConfigurations: IDefaultConfigurationExtension[]): IConfigurationNode {
const configurationNode: IConfigurationNode = {
id: 'defaultOverrides',
title: nls.localize('defaultConfigurations.title', "Default Configuration Overrides"),
@@ -159,9 +170,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
}
}
}
if (Object.keys(configurationNode.properties).length) {
this.registerConfiguration(configurationNode, false);
}
return Object.keys(configurationNode.properties).length ? configurationNode : null;
}
private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, scope: ConfigurationScope = ConfigurationScope.WINDOW, overridable: boolean = false): string[] {
@@ -190,8 +199,17 @@ class ConfigurationRegistry implements IConfigurationRegistry {
if (property.scope === void 0) {
property.scope = scope;
}
// add to properties map
this.configurationProperties[key] = properties[key];
// Add to properties maps
// Property is included by default if 'included' is unspecified
if (properties[key].hasOwnProperty('included') && !properties[key].included) {
this.excludedConfigurationProperties[key] = properties[key];
delete properties[key];
continue;
} else {
this.configurationProperties[key] = properties[key];
}
propertyKeys.push(key);
}
}
@@ -204,10 +222,6 @@ class ConfigurationRegistry implements IConfigurationRegistry {
return propertyKeys;
}
validateProperty(property: string): boolean {
return !OVERRIDE_PROPERTY_PATTERN.test(property) && this.getConfigurationProperties()[property] !== void 0;
}
getConfigurations(): IConfigurationNode[] {
return this.configurationContributors;
}
@@ -216,13 +230,17 @@ class ConfigurationRegistry implements IConfigurationRegistry {
return this.configurationProperties;
}
getExcludedConfigurationProperties(): { [qualifiedKey: string]: IConfigurationPropertySchema } {
return this.excludedConfigurationProperties;
}
private registerJSONConfiguration(configuration: IConfigurationNode) {
function register(configuration: IConfigurationNode) {
let properties = configuration.properties;
if (properties) {
for (let key in properties) {
settingsSchema.properties[key] = properties[key];
resourceSettingsSchema.properties[key] = clone(properties[key]);
resourceSettingsSchema.properties[key] = deepClone(properties[key]);
if (properties[key].scope !== ConfigurationScope.RESOURCE) {
resourceSettingsSchema.properties[key].doNotSuggest = true;
}
@@ -232,7 +250,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
if (subNodes) {
subNodes.forEach(register);
}
};
}
register(configuration);
}
@@ -321,4 +339,4 @@ export function validateProperty(property: string): string {
export function getScopes(keys: string[]): ConfigurationScope[] {
const configurationProperties = configurationRegistry.getConfigurationProperties();
return keys.map(key => configurationProperties[key].scope);
}
}

View File

@@ -0,0 +1,44 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { onUnexpectedError } from 'vs/base/common/errors';
import { ConfigurationModelParser, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels';
import { ConfigWatcher } from 'vs/base/node/config';
import Event, { Emitter } from 'vs/base/common/event';
import { TPromise } from 'vs/base/common/winjs.base';
export class UserConfiguration extends Disposable {
private userConfigModelWatcher: ConfigWatcher<ConfigurationModelParser>;
private _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());
readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;
constructor(settingsPath: string) {
super();
this.userConfigModelWatcher = new ConfigWatcher(settingsPath, {
changeBufferDelay: 300, onError: error => onUnexpectedError(error), defaultConfig: new ConfigurationModelParser(settingsPath), parse: (content: string, parseErrors: any[]) => {
const userConfigModelParser = new ConfigurationModelParser(settingsPath);
userConfigModelParser.parse(content);
parseErrors = [...userConfigModelParser.errors];
return userConfigModelParser;
}
});
this._register(this.userConfigModelWatcher);
// Listeners
this._register(this.userConfigModelWatcher.onDidUpdateConfiguration(() => this._onDidChangeConfiguration.fire(this.configurationModel)));
}
get configurationModel(): ConfigurationModel {
return this.userConfigModelWatcher.getConfig().configurationModel;
}
reload(): TPromise<void> {
return new TPromise(c => this.userConfigModelWatcher.reload(() => c(null)));
}
}

View File

@@ -4,25 +4,24 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ConfigWatcher } from 'vs/base/node/config';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { IConfigurationService, IConfigurationChangeEvent, IConfigurationOverrides, ConfigurationTarget, compare, isConfigurationOverrides, IConfigurationData } from 'vs/platform/configuration/common/configuration';
import { CustomConfigurationModel, DefaultConfigurationModel, ConfigurationModel, Configuration, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels';
import { DefaultConfigurationModel, Configuration, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels';
import Event, { Emitter } from 'vs/base/common/event';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { onUnexpectedError } from 'vs/base/common/errors';
import { TPromise } from 'vs/base/common/winjs.base';
import { equals } from 'vs/base/common/objects';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { UserConfiguration } from 'vs/platform/configuration/node/configuration';
export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable {
_serviceBrand: any;
private _configuration: Configuration;
private userConfigModelWatcher: ConfigWatcher<ConfigurationModel>;
private userConfiguration: UserConfiguration;
private _onDidChangeConfiguration: Emitter<IConfigurationChangeEvent> = this._register(new Emitter<IConfigurationChangeEvent>());
readonly onDidChangeConfiguration: Event<IConfigurationChangeEvent> = this._onDidChangeConfiguration.event;
@@ -32,19 +31,12 @@ export class ConfigurationService extends Disposable implements IConfigurationSe
) {
super();
this.userConfigModelWatcher = new ConfigWatcher(environmentService.appSettingsPath, {
changeBufferDelay: 300, onError: error => onUnexpectedError(error), defaultConfig: new CustomConfigurationModel(null, environmentService.appSettingsPath), parse: (content: string, parseErrors: any[]) => {
const userConfigModel = new CustomConfigurationModel(content, environmentService.appSettingsPath);
parseErrors = [...userConfigModel.errors];
return userConfigModel;
}
});
this._register(this.userConfigModelWatcher);
this.userConfiguration = this._register(new UserConfiguration(environmentService.appSettingsPath));
this.reset();
// Listeners
this._register(this.userConfigModelWatcher.onDidUpdateConfiguration(() => this.onDidUpdateConfigModel()));
this._register(this.userConfiguration.onDidChangeConfiguration(() => this.onDidChangeUserConfiguration()));
this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidRegisterConfiguration(configurationProperties => this.onDidRegisterConfiguration(configurationProperties)));
}
@@ -56,24 +48,20 @@ export class ConfigurationService extends Disposable implements IConfigurationSe
return this.configuration.toData();
}
getConfiguration<T>(): T
getConfiguration<T>(section: string): T
getConfiguration<T>(overrides: IConfigurationOverrides): T
getConfiguration<T>(section: string, overrides: IConfigurationOverrides): T
getConfiguration(arg1?: any, arg2?: any): any {
getValue<T>(): T;
getValue<T>(section: string): T;
getValue<T>(overrides: IConfigurationOverrides): T;
getValue<T>(section: string, overrides: IConfigurationOverrides): T;
getValue(arg1?: any, arg2?: any): any {
const section = typeof arg1 === 'string' ? arg1 : void 0;
const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : {};
return this.configuration.getSection(section, overrides, null);
return this.configuration.getValue(section, overrides, null);
}
getValue(key: string, overrides: IConfigurationOverrides = {}): any {
return this.configuration.getValue(key, overrides, null);
}
updateValue(key: string, value: any): TPromise<void>
updateValue(key: string, value: any, overrides: IConfigurationOverrides): TPromise<void>
updateValue(key: string, value: any, target: ConfigurationTarget): TPromise<void>
updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): TPromise<void>
updateValue(key: string, value: any): TPromise<void>;
updateValue(key: string, value: any, overrides: IConfigurationOverrides): TPromise<void>;
updateValue(key: string, value: any, target: ConfigurationTarget): TPromise<void>;
updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): TPromise<void>;
updateValue(key: string, value: any, arg3?: any, arg4?: any): TPromise<void> {
return TPromise.wrapError(new Error('not supported'));
}
@@ -85,7 +73,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe
workspaceFolder: T
value: T
} {
return this.configuration.lookup<T>(key, {}, null);
return this.configuration.inspect<T>(key, {}, null);
}
keys(): {
@@ -99,12 +87,12 @@ export class ConfigurationService extends Disposable implements IConfigurationSe
reloadConfiguration(folder?: IWorkspaceFolder): TPromise<void> {
return folder ? TPromise.as(null) :
new TPromise((c, e) => this.userConfigModelWatcher.reload(() => c(this.onDidUpdateConfigModel())));
this.userConfiguration.reload().then(() => this.onDidChangeUserConfiguration());
}
private onDidUpdateConfigModel(): void {
private onDidChangeUserConfiguration(): void {
let changedKeys = [];
const { added, updated, removed } = compare(this._configuration.user, this.userConfigModelWatcher.getConfig());
const { added, updated, removed } = compare(this._configuration.user, this.userConfiguration.configurationModel);
changedKeys = [...added, ...updated, ...removed];
if (changedKeys.length) {
const oldConfiguartion = this._configuration;
@@ -123,7 +111,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe
private reset(): void {
const defaults = new DefaultConfigurationModel();
const user = this.userConfigModelWatcher.getConfig();
const user = this.userConfiguration.configurationModel;
this._configuration = new Configuration(defaults, user);
}

View File

@@ -5,11 +5,10 @@
'use strict';
import * as assert from 'assert';
import { ConfigurationModel, CustomConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels';
import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import URI from 'vs/base/common/uri';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
suite('ConfigurationModel', () => {
@@ -103,44 +102,6 @@ suite('ConfigurationModel', () => {
assert.deepEqual(testObject.keys, []);
});
test('setValueInOverrides adds to overrides if does not exist', () => {
let testObject = new ConfigurationModel({ 'a': 1, 'b': 1 }, ['a']);
testObject.setValueInOverrides('or', 'a', 2);
assert.deepEqual(testObject.overrides[0].contents, { 'a': 2 });
assert.deepEqual(testObject.override('or').contents, { 'a': 2, 'b': 1 });
});
test('setValueInOverrides adds to overrides if exist', () => {
let testObject = new ConfigurationModel({ 'a': 1, 'b': 1 }, ['a'], [{ identifiers: ['or'], contents: { 'a': 2 } }]);
testObject.setValueInOverrides('or', 'a', 3);
assert.deepEqual(testObject.overrides[0].contents, { 'a': 3 });
assert.deepEqual(testObject.override('or').contents, { 'a': 3, 'b': 1 });
});
test('setValueInOverrides adds a nested key to overrides if exist', () => {
let testObject = new ConfigurationModel({ 'a': 1, 'b': 1 }, ['a'], [{ identifiers: ['or'], contents: { 'a': { 'c': 1 } } }]);
testObject.setValueInOverrides('or', 'a.c', 2);
assert.deepEqual(testObject.overrides[0].contents, { 'a': { 'c': 2 } });
assert.deepEqual(testObject.override('or').contents, { 'a': { 'c': 2 }, 'b': 1 });
});
test('setValueInOverrides adds new overrides if exist', () => {
let testObject = new ConfigurationModel({ 'a': 1, 'b': 1 }, ['a'], [{ identifiers: ['or1'], contents: { 'a': 2 } }]);
testObject.setValueInOverrides('or2', 'b', 2);
assert.deepEqual(testObject.overrides[0].contents, { 'a': 2 });
assert.deepEqual(testObject.overrides[1].contents, { 'b': 2 });
assert.deepEqual(testObject.override('or1').contents, { 'a': 2, 'b': 1 });
assert.deepEqual(testObject.override('or2').contents, { 'a': 1, 'b': 2 });
});
test('get overriding configuration model for an existing identifier', () => {
let testObject = new ConfigurationModel(
{ 'a': { 'b': 1 }, 'f': 1 }, [],
@@ -212,7 +173,7 @@ suite('ConfigurationModel', () => {
let result = base.merge(add);
assert.deepEqual(result.contents, { 'a': { 'b': 2 } });
assert.deepEqual(result.getSectionContents('a'), { 'b': 2 });
assert.deepEqual(result.getValue('a'), { 'b': 2 });
assert.deepEqual(result.keys, ['a.b']);
});
@@ -240,16 +201,16 @@ suite('ConfigurationModel', () => {
test('Test contents while getting an existing property', () => {
let testObject = new ConfigurationModel({ 'a': 1 });
assert.deepEqual(testObject.getSectionContents('a'), 1);
assert.deepEqual(testObject.getValue('a'), 1);
testObject = new ConfigurationModel({ 'a': { 'b': 1 } });
assert.deepEqual(testObject.getSectionContents('a'), { 'b': 1 });
assert.deepEqual(testObject.getValue('a'), { 'b': 1 });
});
test('Test contents are undefined for non existing properties', () => {
const testObject = new ConfigurationModel({ awesome: true });
assert.deepEqual(testObject.getSectionContents('unknownproperty'), undefined);
assert.deepEqual(testObject.getValue('unknownproperty'), undefined);
});
test('Test override gives all content merged with overrides', () => {
@@ -279,86 +240,97 @@ suite('CustomConfigurationModel', () => {
});
test('simple merge using models', () => {
let base = new CustomConfigurationModel(JSON.stringify({ 'a': 1, 'b': 2 }));
let add = new CustomConfigurationModel(JSON.stringify({ 'a': 3, 'c': 4 }));
let result = base.merge(add);
let base = new ConfigurationModelParser('base');
base.parse(JSON.stringify({ 'a': 1, 'b': 2 }));
let add = new ConfigurationModelParser('add');
add.parse(JSON.stringify({ 'a': 3, 'c': 4 }));
let result = base.configurationModel.merge(add.configurationModel);
assert.deepEqual(result.contents, { 'a': 3, 'b': 2, 'c': 4 });
});
test('simple merge with an undefined contents', () => {
let base = new CustomConfigurationModel(JSON.stringify({ 'a': 1, 'b': 2 }));
let add = new CustomConfigurationModel(null);
let result = base.merge(add);
let base = new ConfigurationModelParser('base');
base.parse(JSON.stringify({ 'a': 1, 'b': 2 }));
let add = new ConfigurationModelParser('add');
let result = base.configurationModel.merge(add.configurationModel);
assert.deepEqual(result.contents, { 'a': 1, 'b': 2 });
base = new CustomConfigurationModel(null);
add = new CustomConfigurationModel(JSON.stringify({ 'a': 1, 'b': 2 }));
result = base.merge(add);
base = new ConfigurationModelParser('base');
add = new ConfigurationModelParser('add');
add.parse(JSON.stringify({ 'a': 1, 'b': 2 }));
result = base.configurationModel.merge(add.configurationModel);
assert.deepEqual(result.contents, { 'a': 1, 'b': 2 });
base = new CustomConfigurationModel(null);
add = new CustomConfigurationModel(null);
result = base.merge(add);
base = new ConfigurationModelParser('base');
add = new ConfigurationModelParser('add');
result = base.configurationModel.merge(add.configurationModel);
assert.deepEqual(result.contents, {});
});
test('Recursive merge using config models', () => {
let base = new CustomConfigurationModel(JSON.stringify({ 'a': { 'b': 1 } }));
let add = new CustomConfigurationModel(JSON.stringify({ 'a': { 'b': 2 } }));
let result = base.merge(add);
let base = new ConfigurationModelParser('base');
base.parse(JSON.stringify({ 'a': { 'b': 1 } }));
let add = new ConfigurationModelParser('add');
add.parse(JSON.stringify({ 'a': { 'b': 2 } }));
let result = base.configurationModel.merge(add.configurationModel);
assert.deepEqual(result.contents, { 'a': { 'b': 2 } });
});
test('Test contents while getting an existing property', () => {
let testObject = new CustomConfigurationModel(JSON.stringify({ 'a': 1 }));
assert.deepEqual(testObject.getSectionContents('a'), 1);
let testObject = new ConfigurationModelParser('test');
testObject.parse(JSON.stringify({ 'a': 1 }));
assert.deepEqual(testObject.configurationModel.getValue('a'), 1);
testObject = new CustomConfigurationModel(JSON.stringify({ 'a': { 'b': 1 } }));
assert.deepEqual(testObject.getSectionContents('a'), { 'b': 1 });
testObject.parse(JSON.stringify({ 'a': { 'b': 1 } }));
assert.deepEqual(testObject.configurationModel.getValue('a'), { 'b': 1 });
});
test('Test contents are undefined for non existing properties', () => {
const testObject = new CustomConfigurationModel(JSON.stringify({
const testObject = new ConfigurationModelParser('test');
testObject.parse(JSON.stringify({
awesome: true
}));
assert.deepEqual(testObject.getSectionContents('unknownproperty'), undefined);
assert.deepEqual(testObject.configurationModel.getValue('unknownproperty'), undefined);
});
test('Test contents are undefined for undefined config', () => {
const testObject = new CustomConfigurationModel(null);
const testObject = new ConfigurationModelParser('test');
assert.deepEqual(testObject.getSectionContents('unknownproperty'), undefined);
assert.deepEqual(testObject.configurationModel.getValue('unknownproperty'), undefined);
});
test('Test configWithOverrides gives all content merged with overrides', () => {
const testObject = new CustomConfigurationModel(JSON.stringify({ 'a': 1, 'c': 1, '[b]': { 'a': 2 } }));
const testObject = new ConfigurationModelParser('test');
testObject.parse(JSON.stringify({ 'a': 1, 'c': 1, '[b]': { 'a': 2 } }));
assert.deepEqual(testObject.override('b').contents, { 'a': 2, 'c': 1, '[b]': { 'a': 2 } });
assert.deepEqual(testObject.configurationModel.override('b').contents, { 'a': 2, 'c': 1, '[b]': { 'a': 2 } });
});
test('Test configWithOverrides gives empty contents', () => {
const testObject = new CustomConfigurationModel(null);
const testObject = new ConfigurationModelParser('test');
assert.deepEqual(testObject.override('b').contents, {});
assert.deepEqual(testObject.configurationModel.override('b').contents, {});
});
test('Test update with empty data', () => {
const testObject = new CustomConfigurationModel();
testObject.update('');
const testObject = new ConfigurationModelParser('test');
testObject.parse('');
assert.deepEqual(testObject.contents, {});
assert.deepEqual(testObject.keys, []);
assert.deepEqual(testObject.configurationModel.contents, {});
assert.deepEqual(testObject.configurationModel.keys, []);
testObject.update(null);
testObject.parse(null);
assert.deepEqual(testObject.contents, {});
assert.deepEqual(testObject.keys, []);
assert.deepEqual(testObject.configurationModel.contents, {});
assert.deepEqual(testObject.configurationModel.keys, []);
testObject.update(undefined);
testObject.parse(undefined);
assert.deepEqual(testObject.contents, {});
assert.deepEqual(testObject.keys, []);
assert.deepEqual(testObject.configurationModel.contents, {});
assert.deepEqual(testObject.configurationModel.keys, []);
});
test('Test registering the same property again', () => {
@@ -375,7 +347,7 @@ suite('CustomConfigurationModel', () => {
}
}
});
assert.equal(true, new DefaultConfigurationModel().getSectionContents('a'));
assert.equal(true, new DefaultConfigurationModel().getValue('a'));
});
test('Test registering the language property', () => {
@@ -392,7 +364,7 @@ suite('CustomConfigurationModel', () => {
}
}
});
assert.equal(undefined, new DefaultConfigurationModel().getSectionContents('[a]'));
assert.equal(undefined, new DefaultConfigurationModel().getValue('[a]'));
});
});
@@ -503,48 +475,4 @@ suite('ConfigurationChangeEvent', () => {
assert.ok(actual.affectsConfiguration('[markdown]', URI.file('file2')));
});
});
suite('AllKeysConfigurationChangeEvent', () => {
test('changeEvent affects keys for any resource', () => {
let testObject = new AllKeysConfigurationChangeEvent(['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows'], ConfigurationTarget.USER, null);
assert.deepEqual(testObject.affectedKeys, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']);
assert.ok(testObject.affectsConfiguration('window.zoomLevel'));
assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file('file1')));
assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file('file2')));
assert.ok(testObject.affectsConfiguration('window.restoreFullscreen'));
assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file1')));
assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file2')));
assert.ok(testObject.affectsConfiguration('window.restoreWindows'));
assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file('file2')));
assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file('file1')));
assert.ok(testObject.affectsConfiguration('window.title'));
assert.ok(testObject.affectsConfiguration('window.title', URI.file('file1')));
assert.ok(testObject.affectsConfiguration('window.title', URI.file('file2')));
assert.ok(testObject.affectsConfiguration('window'));
assert.ok(testObject.affectsConfiguration('window', URI.file('file1')));
assert.ok(testObject.affectsConfiguration('window', URI.file('file2')));
assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview'));
assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('file2')));
assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('file1')));
assert.ok(testObject.affectsConfiguration('workbench.editor'));
assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file('file2')));
assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file('file1')));
assert.ok(testObject.affectsConfiguration('workbench'));
assert.ok(testObject.affectsConfiguration('workbench', URI.file('file2')));
assert.ok(testObject.affectsConfiguration('workbench', URI.file('file1')));
assert.ok(!testObject.affectsConfiguration('files'));
assert.ok(!testObject.affectsConfiguration('files', URI.file('file1')));
});
});

View File

@@ -8,10 +8,9 @@
import { TernarySearchTree } from 'vs/base/common/map';
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { EventEmitter } from 'vs/base/common/eventEmitter';
import { getConfigurationKeys, IConfigurationOverrides, IConfigurationService, getConfigurationValue, isConfigurationOverrides } from 'vs/platform/configuration/common/configuration';
export class TestConfigurationService extends EventEmitter implements IConfigurationService {
export class TestConfigurationService implements IConfigurationService {
public _serviceBrand: any;
private configuration = Object.create(null);
@@ -19,23 +18,21 @@ export class TestConfigurationService extends EventEmitter implements IConfigura
private configurationByRoot: TernarySearchTree<any> = TernarySearchTree.forPaths<any>();
public reloadConfiguration<T>(): TPromise<T> {
return TPromise.as(this.getConfiguration());
return TPromise.as(this.getValue());
}
public getConfiguration<C>(arg1?: any, arg2?: any): C {
public getValue(arg1?: any, arg2?: any): any {
if (arg1 && typeof arg1 === 'string') {
return this.inspect(<string>arg1).value;
}
const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : void 0;
if (overrides && overrides.resource) {
const configForResource = this.configurationByRoot.findSubstr(overrides.resource.fsPath);
return configForResource || this.configuration;
}
return this.configuration;
}
public getValue(key: string, overrides?: IConfigurationOverrides): any {
return this.inspect(key).value;
}
public updateValue(key: string, overrides?: IConfigurationOverrides): TPromise<void> {
return TPromise.as(null);
}
@@ -63,7 +60,7 @@ export class TestConfigurationService extends EventEmitter implements IConfigura
workspaceFolder: T
value: T,
} {
const config = this.getConfiguration(undefined, overrides);
const config = this.getValue(undefined, overrides);
return {
value: getConfigurationValue<T>(config, key),

View File

@@ -18,6 +18,7 @@ import { EnvironmentService } from 'vs/platform/environment/node/environmentServ
import extfs = require('vs/base/node/extfs');
import uuid = require('vs/base/common/uuid');
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { mkdirp } from 'vs/base/node/pfs';
class SettingsTestEnvironmentService extends EnvironmentService {
@@ -36,9 +37,9 @@ suite('ConfigurationService - Node', () => {
const newDir = path.join(parentDir, 'config', id);
const testFile = path.join(newDir, 'config.json');
extfs.mkdirp(newDir, 493, (error) => {
callback(testFile, (callback) => extfs.del(parentDir, os.tmpdir(), () => { }, callback));
});
const onMkdirp = error => callback(testFile, (callback) => extfs.del(parentDir, os.tmpdir(), () => { }, callback));
mkdirp(newDir, 493).done(() => onMkdirp(null), error => onMkdirp(error));
}
test('simple', (done: () => void) => {
@@ -47,7 +48,7 @@ suite('ConfigurationService - Node', () => {
const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, testFile));
const config = service.getConfiguration<{ foo: string }>();
const config = service.getValue<{ foo: string }>();
assert.ok(config);
assert.equal(config.foo, 'bar');
@@ -63,7 +64,7 @@ suite('ConfigurationService - Node', () => {
const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, testFile));
const config = service.getConfiguration<{ testworkbench: { editor: { tabs: boolean } } }>();
const config = service.getValue<{ testworkbench: { editor: { tabs: boolean } } }>();
assert.ok(config);
assert.ok(config.testworkbench);
assert.ok(config.testworkbench.editor);
@@ -81,7 +82,7 @@ suite('ConfigurationService - Node', () => {
const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, testFile));
const config = service.getConfiguration<{ foo: string }>();
const config = service.getValue<{ foo: string }>();
assert.ok(config);
service.dispose();
@@ -98,7 +99,7 @@ suite('ConfigurationService - Node', () => {
const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, testFile));
const config = service.getConfiguration<{ foo: string }>();
const config = service.getValue<{ foo: string }>();
assert.ok(config);
service.dispose();
@@ -110,20 +111,20 @@ suite('ConfigurationService - Node', () => {
const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, testFile));
let config = service.getConfiguration<{ foo: string }>();
let config = service.getValue<{ foo: string }>();
assert.ok(config);
assert.equal(config.foo, 'bar');
fs.writeFileSync(testFile, '{ "foo": "changed" }');
// still outdated
config = service.getConfiguration<{ foo: string }>();
config = service.getValue<{ foo: string }>();
assert.ok(config);
assert.equal(config.foo, 'bar');
// force a reload to get latest
service.reloadConfiguration().then(() => {
config = service.getConfiguration<{ foo: string }>();
config = service.getValue<{ foo: string }>();
assert.ok(config);
assert.equal(config.foo, 'changed');
@@ -156,7 +157,7 @@ suite('ConfigurationService - Node', () => {
});
let serviceWithoutFile = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, '__testFile'));
let setting = serviceWithoutFile.getConfiguration<ITestSetting>();
let setting = serviceWithoutFile.getValue<ITestSetting>();
assert.ok(setting);
assert.equal(setting.configuration.service.testSetting, 'isSet');
@@ -166,7 +167,7 @@ suite('ConfigurationService - Node', () => {
const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, testFile));
let setting = service.getConfiguration<ITestSetting>();
let setting = service.getValue<ITestSetting>();
assert.ok(setting);
assert.equal(setting.configuration.service.testSetting, 'isSet');
@@ -174,7 +175,7 @@ suite('ConfigurationService - Node', () => {
fs.writeFileSync(testFile, '{ "configuration.service.testSetting": "isChanged" }');
service.reloadConfiguration().then(() => {
let setting = service.getConfiguration<ITestSetting>();
let setting = service.getValue<ITestSetting>();
assert.ok(setting);
assert.equal(setting.configuration.service.testSetting, 'isChanged');

View File

@@ -7,7 +7,7 @@
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver';
import { IContextKey, IContext, IContextKeyServiceTarget, IContextKeyService, SET_CONTEXT_COMMAND_ID, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IContextKey, IContext, IContextKeyServiceTarget, IContextKeyService, SET_CONTEXT_COMMAND_ID, ContextKeyExpr, IContextKeyChangeEvent } from 'vs/platform/contextkey/common/contextkey';
import { IConfigurationService, IConfigurationChangeEvent, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import Event, { Emitter, debounceEvent } from 'vs/base/common/event';
@@ -37,7 +37,11 @@ export class Context implements IContext {
public removeValue(key: string): boolean {
// console.log('REMOVE ' + key + ' FROM ' + this._id);
return delete this._value[key];
if (key in this._value) {
delete this._value[key];
return true;
}
return false;
}
public getValue<T>(key: string): T {
@@ -51,11 +55,11 @@ export class Context implements IContext {
class ConfigAwareContextValuesContainer extends Context {
private readonly _emitter: Emitter<string>;
private readonly _emitter: Emitter<string | string[]>;
private readonly _subscription: IDisposable;
private readonly _configurationService: IConfigurationService;
constructor(id: number, configurationService: IConfigurationService, emitter: Emitter<string>) {
constructor(id: number, configurationService: IConfigurationService, emitter: Emitter<string | string[]>) {
super(id, null);
this._emitter = emitter;
@@ -86,14 +90,10 @@ class ConfigAwareContextValuesContainer extends Context {
private _initFromConfiguration() {
const config = this._configurationService.getConfiguration();
// remove old config.xyz values
for (let key in this._value) {
if (key.indexOf('config.') === 0) {
delete this._value[key];
}
}
const prefix = 'config.';
const config = this._configurationService.getValue();
const configKeys: { [key: string]: boolean } = Object.create(null);
const configKeysChanged: string[] = [];
// add new value from config
const walk = (obj: any, keys: string[]) => {
@@ -103,8 +103,14 @@ class ConfigAwareContextValuesContainer extends Context {
let value = obj[key];
if (typeof value === 'boolean') {
const configKey = keys.join('.');
const oldValue = this._value[configKey];
this._value[configKey] = value;
this._emitter.fire(configKey);
if (oldValue !== value) {
configKeysChanged.push(configKey);
configKeys[configKey] = true;
} else {
configKeys[configKey] = false;
}
} else if (typeof value === 'object') {
walk(value, keys);
}
@@ -113,6 +119,18 @@ class ConfigAwareContextValuesContainer extends Context {
}
};
walk(config, ['config']);
// remove unused keys
for (let key in this._value) {
if (key.indexOf(prefix) === 0 && configKeys[key] === undefined) {
delete this._value[key];
configKeys[key] = true;
configKeysChanged.push(key);
}
}
// send events
this._emitter.fire(configKeysChanged);
}
}
@@ -146,11 +164,33 @@ class ContextKey<T> implements IContextKey<T> {
}
}
export class ContextKeyChangeEvent implements IContextKeyChangeEvent {
private _keys: string[] = [];
collect(oneOrManyKeys: string | string[]): void {
if (Array.isArray(oneOrManyKeys)) {
this._keys = this._keys.concat(oneOrManyKeys);
} else {
this._keys.push(oneOrManyKeys);
}
}
affectsSome(keys: Set<string>): boolean {
for (const key of this._keys) {
if (keys.has(key)) {
return true;
}
}
return false;
}
}
export abstract class AbstractContextKeyService implements IContextKeyService {
public _serviceBrand: any;
protected _onDidChangeContext: Event<string[]>;
protected _onDidChangeContextKey: Emitter<string>;
protected _onDidChangeContext: Event<IContextKeyChangeEvent>;
protected _onDidChangeContextKey: Emitter<string | string[]>;
protected _myContextId: number;
constructor(myContextId: number) {
@@ -164,14 +204,13 @@ export abstract class AbstractContextKeyService implements IContextKeyService {
return new ContextKey(this, key, defaultValue);
}
public get onDidChangeContext(): Event<string[]> {
public get onDidChangeContext(): Event<IContextKeyChangeEvent> {
if (!this._onDidChangeContext) {
this._onDidChangeContext = debounceEvent(this._onDidChangeContextKey.event, (prev: string[], cur) => {
this._onDidChangeContext = debounceEvent<string | string[], ContextKeyChangeEvent>(this._onDidChangeContextKey.event, (prev, cur) => {
if (!prev) {
prev = [cur];
} else if (prev.indexOf(cur) < 0) {
prev.push(cur);
prev = new ContextKeyChangeEvent();
}
prev.collect(cur);
return prev;
}, 25);
}
@@ -196,7 +235,11 @@ export abstract class AbstractContextKeyService implements IContextKeyService {
}
public setContext(key: string, value: any): void {
if (this.getContextValuesContainer(this._myContextId).setValue(key, value)) {
const myContext = this.getContextValuesContainer(this._myContextId);
if (!myContext) {
return;
}
if (myContext.setValue(key, value)) {
this._onDidChangeContextKey.fire(key);
}
}
@@ -270,7 +313,7 @@ class ScopedContextKeyService extends AbstractContextKeyService {
private _parent: AbstractContextKeyService;
private _domNode: IContextKeyServiceTarget;
constructor(parent: AbstractContextKeyService, emitter: Emitter<string>, domNode?: IContextKeyServiceTarget) {
constructor(parent: AbstractContextKeyService, emitter: Emitter<string | string[]>, domNode?: IContextKeyServiceTarget) {
super(parent.createChildContext());
this._parent = parent;
this._onDidChangeContextKey = emitter;
@@ -288,7 +331,7 @@ class ScopedContextKeyService extends AbstractContextKeyService {
}
}
public get onDidChangeContext(): Event<string[]> {
public get onDidChangeContext(): Event<IContextKeyChangeEvent> {
return this._parent.onDidChangeContext;
}

View File

@@ -321,7 +321,7 @@ export class ContextKeyNotExpr implements ContextKeyExpr {
}
export class ContextKeyAndExpr implements ContextKeyExpr {
private expr: ContextKeyExpr[];
public readonly expr: ContextKeyExpr[];
constructor(expr: ContextKeyExpr[]) {
this.expr = ContextKeyAndExpr._normalizeArr(expr);
@@ -461,11 +461,15 @@ export interface IContextKeyServiceTarget {
export const IContextKeyService = createDecorator<IContextKeyService>('contextKeyService');
export interface IContextKeyChangeEvent {
affectsSome(keys: Set<string>): boolean;
}
export interface IContextKeyService {
_serviceBrand: any;
dispose(): void;
onDidChangeContext: Event<string[]>;
onDidChangeContext: Event<IContextKeyChangeEvent>;
createKey<T>(key: string, defaultValue: T): IContextKey<T>;
contextMatchesRules(rules: ContextKeyExpr): boolean;
getContextKeyValue<T>(key: string): T;

View File

@@ -9,9 +9,8 @@ import 'vs/css!./contextMenuHandler';
import { $, Builder } from 'vs/base/browser/builder';
import { combinedDisposable, IDisposable } from 'vs/base/common/lifecycle';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { IActionRunner, ActionRunner, IAction } from 'vs/base/common/actions';
import { IActionRunner, ActionRunner, IAction, IRunEvent } from 'vs/base/common/actions';
import { Menu } from 'vs/base/browser/ui/menu/menu';
import { EventType } from 'vs/base/common/events';
import Severity from 'vs/base/common/severity';
import { IContextViewService, IContextMenuDelegate } from 'vs/platform/contextview/browser/contextView';
@@ -42,7 +41,7 @@ export class ContextMenuHandler {
let hideViewOnRun = false;
this.toDispose.push(this.actionRunner.addListener(EventType.BEFORE_RUN, (e: any) => {
this.toDispose.push(this.actionRunner.onDidBeforeRun((e: IRunEvent) => {
if (this.telemetryService) {
/* __GDPR__
"workbenchActionExecuted" : {
@@ -53,14 +52,14 @@ export class ContextMenuHandler {
this.telemetryService.publicLog('workbenchActionExecuted', { id: e.action.id, from: 'contextMenu' });
}
hideViewOnRun = !!e.retainActionItem;
hideViewOnRun = !!(<any>e).retainActionItem;
if (!hideViewOnRun) {
this.contextViewService.hideContextView(false);
}
}));
this.toDispose.push(this.actionRunner.addListener(EventType.RUN, (e: any) => {
this.toDispose.push(this.actionRunner.onDidRun((e: IRunEvent) => {
if (hideViewOnRun) {
this.contextViewService.hideContextView(false);
}
@@ -80,7 +79,7 @@ export class ContextMenuHandler {
}
if (container) {
this.$el = $(container);
this.$el.on('mousedown', (e: MouseEvent) => this.onMouseDown(e));
this.$el.on('mousedown', (e: Event) => this.onMouseDown(e as MouseEvent));
}
}
@@ -105,11 +104,11 @@ export class ContextMenuHandler {
actionRunner: this.actionRunner
});
let listener1 = menu.addListener(EventType.CANCEL, (e: any) => {
let listener1 = menu.onDidCancel(() => {
this.contextViewService.hideContextView(true);
});
let listener2 = menu.addListener(EventType.BLUR, (e: any) => {
let listener2 = menu.onDidBlur(() => {
this.contextViewService.hideContextView(true);
});

View File

@@ -1,56 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
export interface ICredentialsArgs {
service: string;
account: string;
secret?: string;
}
export interface ICredentialsChannel extends IChannel {
call(command: 'readSecret', credentials: ICredentialsArgs): TPromise<string>;
call(command: 'writeSecret', credentials: ICredentialsArgs): TPromise<void>;
call(command: 'deleteSecret', credentials: ICredentialsArgs): TPromise<boolean>;
call(command: string, arg?: any): TPromise<any>;
}
export class CredentialsChannel implements ICredentialsChannel {
constructor(private service: ICredentialsService) { }
call(command: string, arg: ICredentialsArgs): TPromise<any> {
switch (command) {
case 'readSecret': return this.service.readSecret(arg.service, arg.account);
case 'writeSecret': return this.service.writeSecret(arg.service, arg.account, arg.secret);
case 'deleteSecret': return this.service.deleteSecret(arg.service, arg.account);
}
return undefined;
}
}
export class CredentialsChannelClient implements ICredentialsService {
_serviceBrand: any;
constructor(private channel: ICredentialsChannel) { }
readSecret(service: string, account: string): TPromise<string | undefined> {
return this.channel.call('readSecret', { service, account });
}
writeSecret(service: string, account: string, secret: string): TPromise<void> {
return this.channel.call('writeSecret', { service, account, secret });
}
deleteSecret(service: string, account: string): TPromise<boolean> {
return this.channel.call('deleteSecret', { service, account });
}
}

View File

@@ -1,34 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
export class CredentialsService implements ICredentialsService {
_serviceBrand: any;
readSecret(service: string, account: string): TPromise<string | undefined> {
return this.getKeytar()
.then(keytar => TPromise.wrap(keytar.getPassword(service, account)))
.then(result => result === null ? undefined : result);
}
writeSecret(service: string, account: string, secret: string): TPromise<void> {
return this.getKeytar()
.then(keytar => TPromise.wrap(keytar.setPassword(service, account, secret)));
}
deleteSecret(service: string, account: string): TPromise<boolean> {
return this.getKeytar()
.then(keytar => TPromise.wrap(keytar.deletePassword(service, account)));
}
private getKeytar() {
// Avoids https://github.com/Microsoft/vscode/issues/33998
return TPromise.wrap(import('keytar'));
}
}

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { LogLevel } from 'vs/platform/log/common/log';
export interface ParsedArgs {
[arg: string]: any;
@@ -11,6 +12,7 @@ export interface ParsedArgs {
_urls?: string[];
help?: boolean;
version?: boolean;
status?: boolean;
wait?: boolean;
waitMarkerFilePath?: string;
diff?: boolean;
@@ -23,7 +25,9 @@ export interface ParsedArgs {
'user-data-dir'?: string;
performance?: boolean;
'prof-startup'?: string;
'prof-startup-prefix'?: string;
verbose?: boolean;
log?: string;
logExtensionHostCommunication?: boolean;
'disable-extensions'?: boolean;
'extensions-dir'?: string;
@@ -47,6 +51,7 @@ export interface ParsedArgs {
'install-source'?: string;
'disable-updates'?: string;
'disable-crash-reporter'?: string;
'skip-add-to-recently-opened'?: boolean;
}
export const IEnvironmentService = createDecorator<IEnvironmentService>('environmentService');
@@ -76,7 +81,9 @@ export interface IEnvironmentService {
appSettingsHome: string;
appSettingsPath: string;
appKeybindingsPath: string;
machineUUID: string;
settingsSearchBuildId: number;
settingsSearchUrl: string;
backupHome: string;
backupWorkspacesPath: string;
@@ -96,19 +103,25 @@ export interface IEnvironmentService {
logExtensionHostCommunication: boolean;
isBuilt: boolean;
verbose: boolean;
wait: boolean;
status: boolean;
performance: boolean;
profileStartup: { prefix: string, dir: string } | undefined;
// logging
logsPath: string;
verbose: boolean;
logLevel: LogLevel;
skipGettingStarted: boolean | undefined;
skipAddToRecentlyOpened: boolean;
mainIPCHandle: string;
sharedIPCHandle: string;
nodeCachedDataDir: string;
installSource: string;
installSourcePath: string;
disableUpdates: boolean;
disableCrashReporter: boolean;
}

View File

@@ -9,6 +9,8 @@ import * as assert from 'assert';
import { firstIndex } from 'vs/base/common/arrays';
import { localize } from 'vs/nls';
import { ParsedArgs } from '../common/environment';
import { isWindows } from 'vs/base/common/platform';
import product from 'vs/platform/node/product';
const options: minimist.Opts = {
string: [
@@ -51,7 +53,9 @@ const options: minimist.Opts = {
'sticky-quickopen',
'disable-telemetry',
'disable-updates',
'disable-crash-reporter'
'disable-crash-reporter',
'skip-add-to-recently-opened',
'status'
],
alias: {
add: 'a',
@@ -60,6 +64,7 @@ const options: minimist.Opts = {
wait: 'w',
diff: 'd',
goto: 'g',
status: 's',
'new-window': 'n',
'reuse-window': 'r',
performance: 'p',
@@ -131,18 +136,22 @@ export const optionsHelp: { [name: string]: string; } = {
'-n, --new-window': localize('newWindow', "Force a new instance of Code."),
'-p, --performance': localize('performance', "Start with the 'Developer: Startup Performance' command enabled."),
'--prof-startup': localize('prof-startup', "Run CPU profiler during startup"),
'--inspect-extensions': localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection uri."),
'--inspect-brk-extensions': localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection uri."),
'-r, --reuse-window': localize('reuseWindow', "Force opening a file or folder in the last active window."),
'--user-data-dir <dir>': localize('userDataDir', "Specifies the directory that user data is kept in, useful when running as root."),
'--log <level>': localize('log', "Log level to use. Default is 'info'. Allowed values are 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'."),
'--verbose': localize('verbose', "Print verbose output (implies --wait)."),
'-w, --wait': localize('wait', "Wait for the files to be closed before returning."),
'--extensions-dir <dir>': localize('extensionHomePath', "Set the root path for extensions."),
'--list-extensions': localize('listExtensions', "List the installed extensions."),
'--show-versions': localize('showVersions', "Show versions of installed extensions, when using --list-extension."),
'--install-extension (<extension-id> | <extension-vsix-path>)': localize('installExtension', "Installs an extension."),
'--uninstall-extension <extension-id>': localize('uninstallExtension', "Uninstalls an extension."),
'--uninstall-extension (<extension-id> | <extension-vsix-path>)': localize('uninstallExtension', "Uninstalls an extension."),
'--enable-proposed-api <extension-id>': localize('experimentalApis', "Enables proposed api features for an extension."),
'--disable-extensions': localize('disableExtensions', "Disable all installed extensions."),
'--disable-gpu': localize('disableGPU', "Disable GPU hardware acceleration."),
'-s, --status': localize('status', "Print process usage and diagnostics information."),
'-v, --version': localize('version', "Print version."),
'-h, --help': localize('help', "Print usage.")
};
@@ -189,6 +198,8 @@ export function buildHelpMessage(fullName: string, name: string, version: string
${ localize('usage', "Usage")}: ${executable} [${localize('options', "options")}] [${localize('paths', 'paths')}...]
${ isWindows ? localize('stdinWindows', "To read output from another program, append '-' (e.g. 'echo Hello World | {0} -')", product.applicationName) : localize('stdinUnix', "To read from stdin, append '-' (e.g. 'ps aux | grep code | {0} -')", product.applicationName)}
${ localize('optionsUpperCase', "Options")}:
${formatOptions(optionsHelp, columns)}`;
}

View File

@@ -8,12 +8,12 @@ import * as crypto from 'crypto';
import * as paths from 'vs/base/node/paths';
import * as os from 'os';
import * as path from 'path';
import * as fs from 'fs';
import URI from 'vs/base/common/uri';
import { generateUuid, isUUID } from 'vs/base/common/uuid';
import { memoize } from 'vs/base/common/decorators';
import pkg from 'vs/platform/node/package';
import product from 'vs/platform/node/product';
import { LogLevel } from 'vs/platform/log/common/log';
import { toLocalISOString } from 'vs/base/common/date';
// Read this before there's any chance it is overwritten
// Related to https://github.com/Microsoft/vscode/issues/30624
@@ -40,10 +40,6 @@ function getIPCHandle(userDataPath: string, type: string): string {
}
}
export function getInstallSourcePath(userDataPath: string): string {
return path.join(userDataPath, 'installSource');
}
export class EnvironmentService implements IEnvironmentService {
_serviceBrand: any;
@@ -55,6 +51,8 @@ export class EnvironmentService implements IEnvironmentService {
get execPath(): string { return this._execPath; }
readonly logsPath: string;
@memoize
get userHome(): string { return os.homedir(); }
@@ -71,6 +69,12 @@ export class EnvironmentService implements IEnvironmentService {
@memoize
get appSettingsPath(): string { return path.join(this.appSettingsHome, 'settings.json'); }
@memoize
get settingsSearchBuildId(): number { return product.settingsSearchBuildId; }
@memoize
get settingsSearchUrl(): string { return product.settingsSearchUrl; }
@memoize
get appKeybindingsPath(): string { return path.join(this.appSettingsHome, 'keybindings.json'); }
@@ -86,6 +90,9 @@ export class EnvironmentService implements IEnvironmentService {
@memoize
get workspacesHome(): string { return path.join(this.userDataPath, 'Workspaces'); }
@memoize
get installSourcePath(): string { return path.join(this.userDataPath, 'installSource'); }
@memoize
get extensionsPath(): string { return parsePathArg(this._args['extensions-dir'], process) || process.env['VSCODE_EXTENSIONS'] || path.join(this.userHome, product.dataFolderName, 'extensions'); }
@@ -99,6 +106,8 @@ export class EnvironmentService implements IEnvironmentService {
get skipGettingStarted(): boolean { return this._args['skip-getting-started']; }
get skipAddToRecentlyOpened(): boolean { return this._args['skip-add-to-recently-opened']; }
@memoize
get debugExtensionHost(): IExtensionHostDebugParams { return parseExtensionHostPort(this._args, this.isBuilt); }
@@ -107,22 +116,39 @@ export class EnvironmentService implements IEnvironmentService {
get isBuilt(): boolean { return !process.env['VSCODE_DEV']; }
get verbose(): boolean { return this._args.verbose; }
@memoize
get logLevel(): LogLevel {
if (this.verbose) {
return LogLevel.Trace;
}
if (typeof this._args.log === 'string') {
const logLevel = this._args.log.toLowerCase();
switch (logLevel) {
case 'trace':
return LogLevel.Trace;
case 'debug':
return LogLevel.Debug;
case 'info':
return LogLevel.Info;
case 'warn':
return LogLevel.Warning;
case 'error':
return LogLevel.Error;
case 'critical':
return LogLevel.Critical;
case 'off':
return LogLevel.Off;
}
}
return LogLevel.Info;
}
get wait(): boolean { return this._args.wait; }
get logExtensionHostCommunication(): boolean { return this._args.logExtensionHostCommunication; }
get performance(): boolean { return this._args.performance; }
@memoize
get profileStartup(): { prefix: string, dir: string } | undefined {
if (this._args['prof-startup']) {
return {
prefix: process.env.VSCODE_PROFILES_PREFIX,
dir: os.homedir()
};
} else {
return undefined;
}
}
get status(): boolean { return this._args.status; }
@memoize
get mainIPCHandle(): string { return getIPCHandle(this.userDataPath, 'main'); }
@@ -136,34 +162,13 @@ export class EnvironmentService implements IEnvironmentService {
get disableUpdates(): boolean { return !!this._args['disable-updates']; }
get disableCrashReporter(): boolean { return !!this._args['disable-crash-reporter']; }
readonly machineUUID: string;
readonly installSource: string;
constructor(private _args: ParsedArgs, private _execPath: string) {
const machineIdPath = path.join(this.userDataPath, 'machineid');
try {
this.machineUUID = fs.readFileSync(machineIdPath, 'utf8');
if (!isUUID(this.machineUUID)) {
throw new Error('Not a UUID');
}
} catch (err) {
this.machineUUID = generateUuid();
try {
fs.writeFileSync(machineIdPath, this.machineUUID, 'utf8');
} catch (err) {
// noop
}
if (!process.env['VSCODE_LOGS']) {
const key = toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '');
process.env['VSCODE_LOGS'] = path.join(this.userDataPath, 'logs', key);
}
try {
this.installSource = fs.readFileSync(getInstallSourcePath(this.userDataPath), 'utf8').slice(0, 30);
} catch (err) {
this.installSource = '';
}
this.logsPath = process.env['VSCODE_LOGS'];
}
}

View File

@@ -41,4 +41,4 @@ suite('EnvironmentService', () => {
assert.equal(parse(['--user-data-dir', './dir'], { cwd: () => '/foo', env: { 'VSCODE_CWD': '/bar' } }), path.resolve('/bar/dir'),
'should use VSCODE_CWD as the cwd when --user-data-dir is specified');
});
});
});

View File

@@ -8,13 +8,14 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { distinct, coalesce } from 'vs/base/common/arrays';
import Event, { Emitter } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
import { adoptToGalleryExtensionId, getIdAndVersionFromLocalExtensionId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement';
import { adoptToGalleryExtensionId, getIdFromLocalExtensionId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
const DISABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/disabled';
const ENABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/enabled';
export class ExtensionEnablementService implements IExtensionEnablementService {
@@ -29,110 +30,221 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
@IStorageService private storageService: IStorageService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IExtensionManagementService private extensionManagementService: IExtensionManagementService
@IExtensionManagementService extensionManagementService: IExtensionManagementService
) {
extensionManagementService.onDidUninstallExtension(this.onDidUninstallExtension, this, this.disposables);
extensionManagementService.onDidUninstallExtension(this._onDidUninstallExtension, this, this.disposables);
}
private get hasWorkspace(): boolean {
return this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY;
}
getGloballyDisabledExtensions(): IExtensionIdentifier[] {
return this.getDisabledExtensions(StorageScope.GLOBAL);
getDisabledExtensions(): TPromise<IExtensionIdentifier[]> {
let result = this._getDisabledExtensions(StorageScope.GLOBAL);
if (this.hasWorkspace) {
for (const e of this._getDisabledExtensions(StorageScope.WORKSPACE)) {
if (!result.some(r => areSameExtensions(r, e))) {
result.push(e);
}
}
const workspaceEnabledExtensions = this._getEnabledExtensions(StorageScope.WORKSPACE);
if (workspaceEnabledExtensions.length) {
result = result.filter(r => !workspaceEnabledExtensions.some(e => areSameExtensions(e, r)));
}
}
return TPromise.as(result);
}
getWorkspaceDisabledExtensions(): IExtensionIdentifier[] {
return this.getDisabledExtensions(StorageScope.WORKSPACE);
}
canEnable(identifier: IExtensionIdentifier): boolean {
getEnablementState(identifier: IExtensionIdentifier): EnablementState {
if (this.environmentService.disableExtensions) {
return false;
return EnablementState.Disabled;
}
if (this.getGloballyDisabledExtensions().some(d => areSameExtensions(d, identifier))) {
return true;
if (this.hasWorkspace) {
if (this._getEnabledExtensions(StorageScope.WORKSPACE).filter(e => areSameExtensions(e, identifier))[0]) {
return EnablementState.WorkspaceEnabled;
}
if (this._getDisabledExtensions(StorageScope.WORKSPACE).filter(e => areSameExtensions(e, identifier))[0]) {
return EnablementState.WorkspaceDisabled;
}
}
if (this.getWorkspaceDisabledExtensions().some(d => areSameExtensions(d, identifier))) {
return true;
if (this._getDisabledExtensions(StorageScope.GLOBAL).filter(e => areSameExtensions(e, identifier))[0]) {
return EnablementState.Disabled;
}
return false;
return EnablementState.Enabled;
}
setEnablement(identifier: IExtensionIdentifier, enable: boolean, workspace: boolean = false): TPromise<boolean> {
if (workspace && !this.hasWorkspace) {
return TPromise.wrapError<boolean>(new Error(localize('noWorkspace', "No workspace.")));
}
canChangeEnablement(): boolean {
return !this.environmentService.disableExtensions;
}
setEnablement(identifier: IExtensionIdentifier, newState: EnablementState): TPromise<boolean> {
if (this.environmentService.disableExtensions) {
return TPromise.wrap(false);
}
if (enable) {
if (workspace) {
return this.enableExtension(identifier, StorageScope.WORKSPACE);
} else {
return this.enableExtension(identifier, StorageScope.GLOBAL);
}
} else {
if (workspace) {
return this.disableExtension(identifier, StorageScope.WORKSPACE);
} else {
return this.disableExtension(identifier, StorageScope.GLOBAL);
}
const workspace = newState === EnablementState.WorkspaceDisabled || newState === EnablementState.WorkspaceEnabled;
if (workspace && !this.hasWorkspace) {
return TPromise.wrapError<boolean>(new Error(localize('noWorkspace', "No workspace.")));
}
const currentState = this.getEnablementState(identifier);
if (currentState === newState) {
return TPromise.as(false);
}
switch (newState) {
case EnablementState.Enabled:
this._enableExtension(identifier);
break;
case EnablementState.Disabled:
this._disableExtension(identifier);
break;
case EnablementState.WorkspaceEnabled:
this._enableExtensionInWorkspace(identifier);
break;
case EnablementState.WorkspaceDisabled:
this._disableExtensionInWorkspace(identifier);
break;
}
this._onEnablementChanged.fire(identifier);
return TPromise.as(true);
}
isEnabled(identifier: IExtensionIdentifier): boolean {
const enablementState = this.getEnablementState(identifier);
return enablementState === EnablementState.WorkspaceEnabled || enablementState === EnablementState.Enabled;
}
migrateToIdentifiers(installed: IExtensionIdentifier[]): void {
this.migrateDisabledExtensions(installed, StorageScope.GLOBAL);
this._migrateDisabledExtensions(installed, StorageScope.GLOBAL);
if (this.hasWorkspace) {
this.migrateDisabledExtensions(installed, StorageScope.WORKSPACE);
this._migrateDisabledExtensions(installed, StorageScope.WORKSPACE);
}
}
private disableExtension(identifier: IExtensionIdentifier, scope: StorageScope): TPromise<boolean> {
let disabledExtensions = this.getDisabledExtensions(scope);
private _enableExtension(identifier: IExtensionIdentifier): void {
this._removeFromDisabledExtensions(identifier, StorageScope.WORKSPACE);
this._removeFromEnabledExtensions(identifier, StorageScope.WORKSPACE);
this._removeFromDisabledExtensions(identifier, StorageScope.GLOBAL);
}
private _disableExtension(identifier: IExtensionIdentifier): void {
this._removeFromDisabledExtensions(identifier, StorageScope.WORKSPACE);
this._removeFromEnabledExtensions(identifier, StorageScope.WORKSPACE);
this._addToDisabledExtensions(identifier, StorageScope.GLOBAL);
}
private _enableExtensionInWorkspace(identifier: IExtensionIdentifier): void {
this._removeFromDisabledExtensions(identifier, StorageScope.WORKSPACE);
this._addToEnabledExtensions(identifier, StorageScope.WORKSPACE);
}
private _disableExtensionInWorkspace(identifier: IExtensionIdentifier): void {
this._addToDisabledExtensions(identifier, StorageScope.WORKSPACE);
this._removeFromEnabledExtensions(identifier, StorageScope.WORKSPACE);
}
private _addToDisabledExtensions(identifier: IExtensionIdentifier, scope: StorageScope): TPromise<boolean> {
if (scope === StorageScope.WORKSPACE && !this.hasWorkspace) {
return TPromise.wrap(false);
}
let disabledExtensions = this._getDisabledExtensions(scope);
if (disabledExtensions.every(e => !areSameExtensions(e, identifier))) {
disabledExtensions.push(identifier);
this.setDisabledExtensions(disabledExtensions, scope, identifier);
this._setDisabledExtensions(disabledExtensions, scope, identifier);
return TPromise.wrap(true);
}
return TPromise.wrap(false);
}
private enableExtension(identifier: IExtensionIdentifier, scope: StorageScope, fireEvent = true): TPromise<boolean> {
let disabledExtensions = this.getDisabledExtensions(scope);
private _removeFromDisabledExtensions(identifier: IExtensionIdentifier, scope: StorageScope): boolean {
if (scope === StorageScope.WORKSPACE && !this.hasWorkspace) {
return false;
}
let disabledExtensions = this._getDisabledExtensions(scope);
for (let index = 0; index < disabledExtensions.length; index++) {
const disabledExtension = disabledExtensions[index];
if (areSameExtensions(disabledExtension, identifier)) {
disabledExtensions.splice(index, 1);
this.setDisabledExtensions(disabledExtensions, scope, identifier, fireEvent);
return TPromise.wrap(true);
this._setDisabledExtensions(disabledExtensions, scope, identifier);
return true;
}
}
return TPromise.wrap(false);
return false;
}
private getDisabledExtensions(scope: StorageScope): IExtensionIdentifier[] {
private _addToEnabledExtensions(identifier: IExtensionIdentifier, scope: StorageScope): boolean {
if (scope === StorageScope.WORKSPACE && !this.hasWorkspace) {
return false;
}
let enabledExtensions = this._getEnabledExtensions(scope);
if (enabledExtensions.every(e => !areSameExtensions(e, identifier))) {
enabledExtensions.push(identifier);
this._setEnabledExtensions(enabledExtensions, scope, identifier);
return true;
}
return false;
}
private _removeFromEnabledExtensions(identifier: IExtensionIdentifier, scope: StorageScope): boolean {
if (scope === StorageScope.WORKSPACE && !this.hasWorkspace) {
return false;
}
let enabledExtensions = this._getEnabledExtensions(scope);
for (let index = 0; index < enabledExtensions.length; index++) {
const disabledExtension = enabledExtensions[index];
if (areSameExtensions(disabledExtension, identifier)) {
enabledExtensions.splice(index, 1);
this._setEnabledExtensions(enabledExtensions, scope, identifier);
return true;
}
}
return false;
}
private _getEnabledExtensions(scope: StorageScope): IExtensionIdentifier[] {
return this._getExtensions(ENABLED_EXTENSIONS_STORAGE_PATH, scope);
}
private _setEnabledExtensions(enabledExtensions: IExtensionIdentifier[], scope: StorageScope, extension: IExtensionIdentifier): void {
this._setExtensions(ENABLED_EXTENSIONS_STORAGE_PATH, enabledExtensions, scope, extension);
}
private _getDisabledExtensions(scope: StorageScope): IExtensionIdentifier[] {
return this._getExtensions(DISABLED_EXTENSIONS_STORAGE_PATH, scope);
}
private _setDisabledExtensions(disabledExtensions: IExtensionIdentifier[], scope: StorageScope, extension: IExtensionIdentifier, fireEvent = true): void {
this._setExtensions(DISABLED_EXTENSIONS_STORAGE_PATH, disabledExtensions, scope, extension, fireEvent);
}
private _getExtensions(storageId: string, scope: StorageScope): IExtensionIdentifier[] {
if (scope === StorageScope.WORKSPACE && !this.hasWorkspace) {
return [];
}
const value = this.storageService.get(DISABLED_EXTENSIONS_STORAGE_PATH, scope, '');
const value = this.storageService.get(storageId, scope, '');
return value ? JSON.parse(value) : [];
}
private setDisabledExtensions(disabledExtensions: IExtensionIdentifier[], scope: StorageScope, extension: IExtensionIdentifier, fireEvent = true): void {
if (disabledExtensions.length) {
this.storageService.store(DISABLED_EXTENSIONS_STORAGE_PATH, JSON.stringify(disabledExtensions.map(({ id, uuid }) => (<IExtensionIdentifier>{ id, uuid }))), scope);
private _setExtensions(storageId: string, extensions: IExtensionIdentifier[], scope: StorageScope, extension: IExtensionIdentifier, fireEvent = true): void {
if (extensions.length) {
this.storageService.store(storageId, JSON.stringify(extensions.map(({ id, uuid }) => (<IExtensionIdentifier>{ id, uuid }))), scope);
} else {
this.storageService.remove(DISABLED_EXTENSIONS_STORAGE_PATH, scope);
this.storageService.remove(storageId, scope);
}
if (fireEvent) {
this._onEnablementChanged.fire(extension);
}
}
private migrateDisabledExtensions(installedExtensions: IExtensionIdentifier[], scope: StorageScope): void {
private _migrateDisabledExtensions(installedExtensions: IExtensionIdentifier[], scope: StorageScope): void {
const oldValue = this.storageService.get('extensions/disabled', scope, '');
if (oldValue) {
const extensionIdentifiers = coalesce(distinct(oldValue.split(',')).map(id => {
@@ -147,13 +259,14 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
this.storageService.remove('extensions/disabled', scope);
}
private onDidUninstallExtension({ identifier, error }: DidUninstallExtensionEvent): void {
private _onDidUninstallExtension({ identifier, error }: DidUninstallExtensionEvent): void {
if (!error) {
const id = getIdAndVersionFromLocalExtensionId(identifier.id).id;
const id = getIdFromLocalExtensionId(identifier.id);
if (id) {
const extension = { id, uuid: identifier.uuid };
this.enableExtension(extension, StorageScope.WORKSPACE, false);
this.enableExtension(extension, StorageScope.GLOBAL, false);
this._removeFromDisabledExtensions(extension, StorageScope.WORKSPACE);
this._removeFromEnabledExtensions(extension, StorageScope.WORKSPACE);
this._removeFromDisabledExtensions(extension, StorageScope.GLOBAL);
}
}
}

View File

@@ -133,6 +133,7 @@ export interface IGalleryExtensionAssets {
download: IGalleryExtensionAsset;
icon: IGalleryExtensionAsset;
license: IGalleryExtensionAsset;
repository: IGalleryExtensionAsset;
}
export interface IExtensionIdentifier {
@@ -156,6 +157,7 @@ export interface IGalleryExtension {
assets: IGalleryExtensionAssets;
properties: IGalleryExtensionProperties;
telemetryData: any;
preview: boolean;
}
export interface IGalleryMetadata {
@@ -213,6 +215,11 @@ export enum StatisticType {
Uninstall = 'uninstall'
}
export interface IReportedExtension {
id: IExtensionIdentifier;
malicious: boolean;
}
export interface IExtensionGalleryService {
_serviceBrand: any;
isEnabled(): boolean;
@@ -223,7 +230,8 @@ export interface IExtensionGalleryService {
getManifest(extension: IGalleryExtension): TPromise<IExtensionManifest>;
getChangelog(extension: IGalleryExtension): TPromise<string>;
loadCompatibleVersion(extension: IGalleryExtension): TPromise<IGalleryExtension>;
getAllDependencies(extension: IGalleryExtension): TPromise<IGalleryExtension[]>;
loadAllDependencies(dependencies: IExtensionIdentifier[]): TPromise<IGalleryExtension[]>;
getExtensionsReport(): TPromise<IReportedExtension[]>;
}
export interface InstallExtensionEvent {
@@ -257,10 +265,18 @@ export interface IExtensionManagementService {
installFromGallery(extension: IGalleryExtension): TPromise<void>;
uninstall(extension: ILocalExtension, force?: boolean): TPromise<void>;
getInstalled(type?: LocalExtensionType): TPromise<ILocalExtension[]>;
getExtensionsReport(): TPromise<IReportedExtension[]>;
updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): TPromise<ILocalExtension>;
}
export enum EnablementState {
Disabled,
WorkspaceDisabled,
Enabled,
WorkspaceEnabled
}
export const IExtensionEnablementService = createDecorator<IExtensionEnablementService>('extensionEnablementService');
// TODO: @sandy: Merge this into IExtensionManagementService when we have a storage service available in Shared process
@@ -273,21 +289,25 @@ export interface IExtensionEnablementService {
onEnablementChanged: Event<IExtensionIdentifier>;
/**
* Returns all globally disabled extension identifiers.
* Returns an empty array if none exist.
* Returns all disabled extension identifiers for current workspace
* Returns an empty array if none exist
*/
getGloballyDisabledExtensions(): IExtensionIdentifier[];
getDisabledExtensions(): TPromise<IExtensionIdentifier[]>;
/**
* Returns all workspace disabled extension identifiers.
* Returns an empty array if none exist or workspace does not exist.
* Returns the enablement state for the given extension
*/
getWorkspaceDisabledExtensions(): IExtensionIdentifier[];
getEnablementState(identifier: IExtensionIdentifier): EnablementState;
/**
* Returns `true` if given extension can be enabled by calling `setEnablement`, otherwise false`.
* Returns `true` if the enablement can be changed.
*/
canEnable(identifier: IExtensionIdentifier): boolean;
canChangeEnablement(): boolean;
/**
* Returns `true` if the given extension identifier is enabled.
*/
isEnabled(identifier: IExtensionIdentifier): boolean;
/**
* Enable or disable the given extension.
@@ -298,7 +318,7 @@ export interface IExtensionEnablementService {
*
* Throws error if enablement is requested for workspace and there is no workspace
*/
setEnablement(identifier: IExtensionIdentifier, enable: boolean, workspace?: boolean): TPromise<boolean>;
setEnablement(identifier: IExtensionIdentifier, state: EnablementState): TPromise<boolean>;
migrateToIdentifiers(installed: IExtensionIdentifier[]): void;
}
@@ -318,4 +338,4 @@ export interface IExtensionTipsService {
export const ExtensionsLabel = localize('extensions', "Extensions");
export const ExtensionsChannelId = 'extensions';
export const PreferencesLabel = localize('preferences', "Preferences");
export const PreferencesLabel = localize('preferences', "Preferences");

View File

@@ -7,7 +7,7 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc';
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, LocalExtensionType, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata } from './extensionManagement';
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, LocalExtensionType, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension } from './extensionManagement';
import Event, { buffer } from 'vs/base/common/event';
export interface IExtensionManagementChannel extends IChannel {
@@ -19,6 +19,7 @@ export interface IExtensionManagementChannel extends IChannel {
call(command: 'installFromGallery', extension: IGalleryExtension): TPromise<void>;
call(command: 'uninstall', args: [ILocalExtension, boolean]): TPromise<void>;
call(command: 'getInstalled'): TPromise<ILocalExtension[]>;
call(command: 'getExtensionsReport'): TPromise<IReportedExtension[]>;
call(command: string, arg?: any): TPromise<any>;
}
@@ -47,6 +48,7 @@ export class ExtensionManagementChannel implements IExtensionManagementChannel {
case 'uninstall': return this.service.uninstall(arg[0], arg[1]);
case 'getInstalled': return this.service.getInstalled(arg);
case 'updateMetadata': return this.service.updateMetadata(arg[0], arg[1]);
case 'getExtensionsReport': return this.service.getExtensionsReport();
}
return undefined;
}
@@ -89,4 +91,8 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): TPromise<ILocalExtension> {
return this.channel.call('updateMetadata', [local, metadata]);
}
getExtensionsReport(): TPromise<IReportedExtension[]> {
return this.channel.call('getExtensionsReport');
}
}

View File

@@ -5,8 +5,7 @@
'use strict';
import { ILocalExtension, IGalleryExtension, EXTENSION_IDENTIFIER_REGEX, IExtensionEnablementService, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ILocalExtension, IGalleryExtension, EXTENSION_IDENTIFIER_REGEX, IExtensionIdentifier, IReportedExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
export function areSameExtensions(a: IExtensionIdentifier, b: IExtensionIdentifier): boolean {
if (a.uuid && b.uuid) {
@@ -26,15 +25,14 @@ export function getGalleryExtensionIdFromLocal(local: ILocalExtension): string {
return getGalleryExtensionId(local.manifest.publisher, local.manifest.name);
}
export function getIdAndVersionFromLocalExtensionId(localExtensionId: string): { id: string, version: string } {
const matches = /^([^.]+\..+)-(\d+\.\d+\.\d+)$/.exec(localExtensionId);
if (matches && matches[1] && matches[2]) {
return { id: adoptToGalleryExtensionId(matches[1]), version: matches[2] };
export const LOCAL_EXTENSION_ID_REGEX = /^([^.]+\..+)-(\d+\.\d+\.\d+(-.*)?)$/;
export function getIdFromLocalExtensionId(localExtensionId: string): string {
const matches = LOCAL_EXTENSION_ID_REGEX.exec(localExtensionId);
if (matches && matches[1]) {
return adoptToGalleryExtensionId(matches[1]);
}
return {
id: adoptToGalleryExtensionId(localExtensionId),
version: null
};
return adoptToGalleryExtensionId(localExtensionId);
}
export function adoptToGalleryExtensionId(id: string): string {
@@ -81,23 +79,17 @@ export function getGalleryExtensionTelemetryData(extension: IGalleryExtension):
};
}
const BetterMergeCheckKey = 'extensions/bettermergecheck';
export const BetterMergeDisabledNowKey = 'extensions/bettermergedisablednow';
export const BetterMergeId = 'pprice.better-merge';
/**
* Globally disabled extensions, taking care of disabling obsolete extensions.
*/
export function getGloballyDisabledExtensions(extensionEnablementService: IExtensionEnablementService, storageService: IStorageService, installedExtensions: { id: string; }[]) {
const globallyDisabled = extensionEnablementService.getGloballyDisabledExtensions();
if (!storageService.getBoolean(BetterMergeCheckKey, StorageScope.GLOBAL, false)) {
storageService.store(BetterMergeCheckKey, true);
if (globallyDisabled.every(disabled => disabled.id !== BetterMergeId) && installedExtensions.some(d => d.id === BetterMergeId)) {
globallyDisabled.push({ id: BetterMergeId });
extensionEnablementService.setEnablement({ id: BetterMergeId }, false);
storageService.store(BetterMergeDisabledNowKey, true);
export function getMaliciousExtensionsSet(report: IReportedExtension[]): Set<string> {
const result = new Set<string>();
for (const extension of report) {
if (extension.malicious) {
result.add(extension.id.id);
}
}
return globallyDisabled;
return result;
}

View File

@@ -8,7 +8,7 @@
import { cloneAndChange } from 'vs/base/common/objects';
import { IExtensionManifest } from 'vs/platform/extensionManagement/common/extensionManagement';
const nlsRegex = /^%([\w\d.]+)%$/i;
const nlsRegex = /^%([\w\d.-]+)%$/i;
export interface ITranslations {
[key: string]: string;

View File

@@ -7,21 +7,23 @@ import { localize } from 'vs/nls';
import { tmpdir } from 'os';
import * as path from 'path';
import { TPromise } from 'vs/base/common/winjs.base';
import * as uuid from 'vs/base/common/uuid';
import { distinct } from 'vs/base/common/arrays';
import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors';
import { StatisticType, IGalleryExtension, IExtensionGalleryService, IGalleryExtensionAsset, IQueryOptions, SortBy, SortOrder, IExtensionManifest } from 'vs/platform/extensionManagement/common/extensionManagement';
import { StatisticType, IGalleryExtension, IExtensionGalleryService, IGalleryExtensionAsset, IQueryOptions, SortBy, SortOrder, IExtensionManifest, IExtensionIdentifier, IReportedExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionId, getGalleryExtensionTelemetryData, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { assign, getOrDefault } from 'vs/base/common/objects';
import { IRequestService } from 'vs/platform/request/node/request';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IPager } from 'vs/base/common/paging';
import { IRequestOptions, IRequestContext, download, asJson, asText } from 'vs/base/node/request';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import pkg from 'vs/platform/node/package';
import product from 'vs/platform/node/product';
import { isVersionValid } from 'vs/platform/extensions/node/extensionValidator';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { readFile } from 'vs/base/node/pfs';
import { writeFileAndFlushSync } from 'vs/base/node/extfs';
import { generateUuid, isUUID } from 'vs/base/common/uuid';
import { values } from 'vs/base/common/map';
interface IRawGalleryExtensionFile {
assetType: string;
@@ -55,6 +57,7 @@ interface IRawGalleryExtension {
publisher: { displayName: string, publisherId: string, publisherName: string; };
versions: IRawGalleryExtensionVersion[];
statistics: IRawGalleryExtensionStatistics[];
flags: string;
}
interface IRawGalleryQueryResult {
@@ -107,6 +110,7 @@ const AssetType = {
Manifest: 'Microsoft.VisualStudio.Code.Manifest',
VSIX: 'Microsoft.VisualStudio.Services.VSIXPackage',
License: 'Microsoft.VisualStudio.Services.Content.License',
Repository: 'Microsoft.VisualStudio.Services.Links.Source'
};
const PropertyType = {
@@ -200,6 +204,26 @@ function getStatistic(statistics: IRawGalleryExtensionStatistics[], name: string
function getVersionAsset(version: IRawGalleryExtensionVersion, type: string): IGalleryExtensionAsset {
const result = version.files.filter(f => f.assetType === type)[0];
if (type === AssetType.Repository) {
if (version.properties) {
const results = version.properties.filter(p => p.key === type);
const gitRegExp = new RegExp('((git|ssh|http(s)?)|(git@[\w\.]+))(:(//)?)([\w\.@\:/\-~]+)(\.git)(/)?');
const uri = results.filter(r => gitRegExp.test(r.value))[0];
if (!uri) {
return {
uri: null,
fallbackUri: null
};
}
return {
uri: uri.value,
fallbackUri: uri.value,
};
}
}
if (!result) {
if (type === AssetType.Icon) {
const uri = require.toUrl('./media/defaultIcon.png');
@@ -233,6 +257,10 @@ function getEngine(version: IRawGalleryExtensionVersion): string {
return (values.length > 0 && values[0].value) || '';
}
function getIsPreview(flags: string): boolean {
return flags.indexOf('preview') !== -1;
}
function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUrl: string, index: number, query: Query, querySource?: string): IGalleryExtension {
const [version] = galleryExtension.versions;
const assets = {
@@ -241,7 +269,8 @@ function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUr
changelog: getVersionAsset(version, AssetType.Changelog),
download: getVersionAsset(version, AssetType.VSIX),
icon: getVersionAsset(version, AssetType.Icon),
license: getVersionAsset(version, AssetType.License)
license: getVersionAsset(version, AssetType.License),
repository: getVersionAsset(version, AssetType.Repository),
};
return {
@@ -276,31 +305,34 @@ function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUr
index: ((query.pageNumber - 1) * query.pageSize) + index,
searchText: query.searchText,
querySource
}
},
preview: getIsPreview(galleryExtension.flags)
};
}
interface IRawExtensionsReport {
malicious: string[];
slow: string[];
}
export class ExtensionGalleryService implements IExtensionGalleryService {
_serviceBrand: any;
private extensionsGalleryUrl: string;
private extensionsControlUrl: string;
private readonly commonHTTPHeaders: { [key: string]: string; };
private readonly commonHeadersPromise: TPromise<{ [key: string]: string; }>;
constructor(
@IRequestService private requestService: IRequestService,
@IEnvironmentService private environmentService: IEnvironmentService,
@ITelemetryService private telemetryService: ITelemetryService,
@IConfigurationService private configurationService: IConfigurationService
@ITelemetryService private telemetryService: ITelemetryService
) {
const config = product.extensionsGallery;
this.extensionsGalleryUrl = config && config.serviceUrl;
this.commonHTTPHeaders = {
'X-Market-Client-Id': `VSCode ${pkg.version}`,
'User-Agent': `VSCode ${pkg.version}`,
'X-Market-User-Id': this.environmentService.machineUUID
};
this.extensionsControlUrl = config && config.controlUrl;
this.commonHeadersPromise = resolveMarketplaceHeaders(this.environmentService);
}
private api(path = ''): string {
@@ -386,33 +418,34 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
}
private queryGallery(query: Query): TPromise<{ galleryExtensions: IRawGalleryExtension[], total: number; }> {
const commonHeaders = this.commonHTTPHeaders;
const data = JSON.stringify(query.raw);
const headers = assign({}, commonHeaders, {
'Content-Type': 'application/json',
'Accept': 'application/json;api-version=3.0-preview.1',
'Accept-Encoding': 'gzip',
'Content-Length': data.length
});
return this.commonHeadersPromise.then(commonHeaders => {
const data = JSON.stringify(query.raw);
const headers = assign({}, commonHeaders, {
'Content-Type': 'application/json',
'Accept': 'application/json;api-version=3.0-preview.1',
'Accept-Encoding': 'gzip',
'Content-Length': data.length
});
return this.requestService.request({
type: 'POST',
url: this.api('/extensionquery'),
data,
headers
}).then(context => {
return this.requestService.request({
type: 'POST',
url: this.api('/extensionquery'),
data,
headers
}).then(context => {
if (context.res.statusCode >= 400 && context.res.statusCode < 500) {
return { galleryExtensions: [], total: 0 };
}
if (context.res.statusCode >= 400 && context.res.statusCode < 500) {
return { galleryExtensions: [], total: 0 };
}
return asJson<IRawGalleryQueryResult>(context).then(result => {
const r = result.results[0];
const galleryExtensions = r.extensions;
const resultCount = r.resultMetadata && r.resultMetadata.filter(m => m.metadataType === 'ResultCount')[0];
const total = resultCount && resultCount.metadataItems.filter(i => i.name === 'TotalCount')[0].count || 0;
return asJson<IRawGalleryQueryResult>(context).then(result => {
const r = result.results[0];
const galleryExtensions = r.extensions;
const resultCount = r.resultMetadata && r.resultMetadata.filter(m => m.metadataType === 'ResultCount')[0];
const total = resultCount && resultCount.metadataItems.filter(i => i.name === 'TotalCount')[0].count || 0;
return { galleryExtensions, total };
return { galleryExtensions, total };
});
});
});
}
@@ -422,35 +455,41 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return TPromise.as(null);
}
const headers = { ...this.commonHTTPHeaders, Accept: '*/*;api-version=4.0-preview.1' };
return this.commonHeadersPromise.then(commonHeaders => {
const headers = { ...commonHeaders, Accept: '*/*;api-version=4.0-preview.1' };
return this.requestService.request({
type: 'POST',
url: this.api(`/publishers/${publisher}/extensions/${name}/${version}/stats?statType=${type}`),
headers
}).then(null, () => null);
return this.requestService.request({
type: 'POST',
url: this.api(`/publishers/${publisher}/extensions/${name}/${version}/stats?statType=${type}`),
headers
}).then(null, () => null);
});
}
download(extension: IGalleryExtension): TPromise<string> {
return this.loadCompatibleVersion(extension).then(extension => {
const zipPath = path.join(tmpdir(), uuid.generateUuid());
const data = getGalleryExtensionTelemetryData(extension);
const startTime = new Date().getTime();
/* __GDPR__
"galleryService:downloadVSIX" : {
"duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
return this.loadCompatibleVersion(extension)
.then(extension => {
if (!extension) {
return TPromise.wrapError(new Error(localize('notCompatibleDownload', "Unable to download because the extension compatible with current version '{0}' of VS Code is not found.", pkg.version)));
}
*/
const log = (duration: number) => this.telemetryService.publicLog('galleryService:downloadVSIX', assign(data, { duration }));
const zipPath = path.join(tmpdir(), generateUuid());
const data = getGalleryExtensionTelemetryData(extension);
const startTime = new Date().getTime();
/* __GDPR__
"galleryService:downloadVSIX" : {
"duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
}
*/
const log = (duration: number) => this.telemetryService.publicLog('galleryService:downloadVSIX', assign(data, { duration }));
return this.getAsset(extension.assets.download)
.then(context => download(zipPath, context))
.then(() => log(new Date().getTime() - startTime))
.then(() => zipPath);
});
return this.getAsset(extension.assets.download)
.then(context => download(zipPath, context))
.then(() => log(new Date().getTime() - startTime))
.then(() => zipPath);
});
}
getReadme(extension: IGalleryExtension): TPromise<string> {
@@ -469,9 +508,8 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
.then(asText);
}
getAllDependencies(extension: IGalleryExtension): TPromise<IGalleryExtension[]> {
return this.loadCompatibleVersion(<IGalleryExtension>extension)
.then(compatible => this.getDependenciesReccursively(compatible.properties.dependencies, [], extension));
loadAllDependencies(extensions: IExtensionIdentifier[]): TPromise<IGalleryExtension[]> {
return this.getDependenciesReccursively(extensions.map(e => e.id), []);
}
loadCompatibleVersion(extension: IGalleryExtension): TPromise<IGalleryExtension> {
@@ -487,22 +525,26 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
.withAssetTypes(AssetType.Manifest, AssetType.VSIX)
.withFilter(FilterType.ExtensionId, extension.identifier.uuid);
return this.queryGallery(query).then(({ galleryExtensions }) => {
const [rawExtension] = galleryExtensions;
return this.queryGallery(query)
.then(({ galleryExtensions }) => {
const [rawExtension] = galleryExtensions;
if (!rawExtension) {
return TPromise.wrapError<IGalleryExtension>(new Error(localize('notFound', "Extension not found")));
}
if (!rawExtension) {
return null;
}
return this.getLastValidExtensionVersion(rawExtension, rawExtension.versions)
.then(rawVersion => {
extension.properties.dependencies = getDependencies(rawVersion);
extension.properties.engine = getEngine(rawVersion);
extension.assets.download = getVersionAsset(rawVersion, AssetType.VSIX);
extension.version = rawVersion.version;
return extension;
});
});
return this.getLastValidExtensionVersion(rawExtension, rawExtension.versions)
.then(rawVersion => {
if (rawVersion) {
extension.properties.dependencies = getDependencies(rawVersion);
extension.properties.engine = getEngine(rawVersion);
extension.assets.download = getVersionAsset(rawVersion, AssetType.VSIX);
extension.version = rawVersion.version;
return extension;
}
return null;
});
});
}
private loadDependencies(extensionNames: string[]): TPromise<IGalleryExtension[]> {
@@ -533,13 +575,10 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
});
}
private getDependenciesReccursively(toGet: string[], result: IGalleryExtension[], root: IGalleryExtension): TPromise<IGalleryExtension[]> {
private getDependenciesReccursively(toGet: string[], result: IGalleryExtension[]): TPromise<IGalleryExtension[]> {
if (!toGet || !toGet.length) {
return TPromise.wrap(result);
}
if (toGet.indexOf(`${root.publisher}.${root.name}`) !== -1 && result.indexOf(root) === -1) {
result.push(root);
}
toGet = result.length ? toGet.filter(e => !ExtensionGalleryService.hasExtensionByName(result, e)) : toGet;
if (!toGet.length) {
return TPromise.wrap(result);
@@ -556,52 +595,30 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
result = distinct(result.concat(loadedDependencies), d => d.identifier.uuid);
const dependencies: string[] = [];
dependenciesSet.forEach(d => !ExtensionGalleryService.hasExtensionByName(result, d) && dependencies.push(d));
return this.getDependenciesReccursively(dependencies, result, root);
return this.getDependenciesReccursively(dependencies, result);
});
}
private getAsset(asset: IGalleryExtensionAsset, options: IRequestOptions = {}): TPromise<IRequestContext> {
const baseOptions = { type: 'GET' };
const headers = assign({}, this.commonHTTPHeaders, options.headers || {});
options = assign({}, options, baseOptions, { headers });
return this.commonHeadersPromise.then(commonHeaders => {
const baseOptions = { type: 'GET' };
const headers = assign({}, commonHeaders, options.headers || {});
options = assign({}, options, baseOptions, { headers });
const url = asset.uri;
const fallbackUrl = asset.fallbackUri;
const firstOptions = assign({}, options, { url });
const url = asset.uri;
const fallbackUrl = asset.fallbackUri;
const firstOptions = assign({}, options, { url });
return this.requestService.request(firstOptions)
.then(context => {
if (context.res.statusCode === 200) {
return TPromise.as(context);
}
return asText(context)
.then(message => TPromise.wrapError<IRequestContext>(new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`)));
})
.then(null, err => {
if (isPromiseCanceledError(err)) {
return TPromise.wrapError<IRequestContext>(err);
}
const message = getErrorMessage(err);
/* __GDPR__
"galleryService:requestError" : {
"url" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"cdn": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"message": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
return this.requestService.request(firstOptions)
.then(context => {
if (context.res.statusCode === 200) {
return TPromise.as(context);
}
*/
this.telemetryService.publicLog('galleryService:requestError', { url, cdn: true, message });
/* __GDPR__
"galleryService:cdnFallback" : {
"url" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"message": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('galleryService:cdnFallback', { url, message });
const fallbackOptions = assign({}, options, { url: fallbackUrl });
return this.requestService.request(fallbackOptions).then(null, err => {
return asText(context)
.then(message => TPromise.wrapError<IRequestContext>(new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`)));
})
.then(null, err => {
if (isPromiseCanceledError(err)) {
return TPromise.wrapError<IRequestContext>(err);
}
@@ -614,10 +631,34 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
"message": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('galleryService:requestError', { url: fallbackUrl, cdn: false, message });
return TPromise.wrapError<IRequestContext>(err);
this.telemetryService.publicLog('galleryService:requestError', { url, cdn: true, message });
/* __GDPR__
"galleryService:cdnFallback" : {
"url" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"message": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('galleryService:cdnFallback', { url, message });
const fallbackOptions = assign({}, options, { url: fallbackUrl });
return this.requestService.request(fallbackOptions).then(null, err => {
if (isPromiseCanceledError(err)) {
return TPromise.wrapError<IRequestContext>(err);
}
const message = getErrorMessage(err);
/* __GDPR__
"galleryService:requestError" : {
"url" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"cdn": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"message": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('galleryService:requestError', { url: fallbackUrl, cdn: false, message });
return TPromise.wrapError<IRequestContext>(err);
});
});
});
});
}
private getLastValidExtensionVersion(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): TPromise<IRawGalleryExtensionVersion> {
@@ -643,7 +684,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
private getLastValidExtensionVersionReccursively(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): TPromise<IRawGalleryExtensionVersion> {
if (!versions.length) {
return TPromise.wrapError<IRawGalleryExtensionVersion>(new Error(localize('noCompatible', "Couldn't find a compatible version of {0} with this version of Code.", extension.displayName || extension.extensionName)));
return null;
}
const version = versions[0];
@@ -678,4 +719,62 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
}
return false;
}
getExtensionsReport(): TPromise<IReportedExtension[]> {
if (!this.isEnabled()) {
return TPromise.wrapError(new Error('No extension gallery service configured.'));
}
if (!this.extensionsControlUrl) {
return TPromise.as([]);
}
return this.requestService.request({ type: 'GET', url: this.extensionsControlUrl }).then(context => {
if (context.res.statusCode !== 200) {
return TPromise.wrapError(new Error('Could not get extensions report.'));
}
return asJson<IRawExtensionsReport>(context).then(result => {
const map = new Map<string, IReportedExtension>();
for (const id of result.malicious) {
const ext = map.get(id) || { id: { id }, malicious: true, slow: false };
ext.malicious = true;
map.set(id, ext);
}
return TPromise.as(values(map));
});
});
}
}
export function resolveMarketplaceHeaders(environmentService: IEnvironmentService): TPromise<{ [key: string]: string; }> {
const marketplaceMachineIdFile = path.join(environmentService.userDataPath, 'machineid');
return readFile(marketplaceMachineIdFile, 'utf8').then(contents => {
if (isUUID(contents)) {
return contents;
}
return TPromise.wrap(null); // invalid marketplace UUID
}, error => {
return TPromise.wrap(null); // error reading ID file
}).then(uuid => {
if (!uuid) {
uuid = generateUuid();
try {
writeFileAndFlushSync(marketplaceMachineIdFile, uuid);
} catch (error) {
//noop
}
}
return {
'X-Market-Client-Id': `VSCode ${pkg.version}`,
'User-Agent': `VSCode ${pkg.version}`,
'X-Market-User-Id': uuid
};
});
}

View File

@@ -19,9 +19,11 @@ import {
IGalleryExtension, IExtensionManifest, IGalleryMetadata,
InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, LocalExtensionType,
StatisticType,
IExtensionIdentifier
IExtensionIdentifier,
IReportedExtension
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionIdFromLocal, getIdAndVersionFromLocalExtensionId, adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { getGalleryExtensionIdFromLocal, adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
import { localizeManifest } from '../common/extensionNls';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { Limiter } from 'vs/base/common/async';
@@ -30,11 +32,26 @@ import * as semver from 'semver';
import { groupBy, values } from 'vs/base/common/collections';
import URI from 'vs/base/common/uri';
import { IChoiceService, Severity } from 'vs/platform/message/common/message';
import pkg from 'vs/platform/node/package';
import { isMacintosh } from 'vs/base/common/platform';
import { MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE } from 'vs/platform/extensions/common/extensions';
import { ILogService } from 'vs/platform/log/common/log';
const SystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'extensions'));
const INSTALL_ERROR_OBSOLETE = 'obsolete';
const INSTALL_ERROR_INCOMPATIBLE = 'incompatible';
const INSTALL_ERROR_DOWNLOADING = 'downloading';
const INSTALL_ERROR_VALIDATING = 'validating';
const INSTALL_ERROR_GALLERY = 'gallery';
const INSTALL_ERROR_LOCAL = 'local';
const INSTALL_ERROR_EXTRACTING = 'extracting';
const INSTALL_ERROR_UNKNOWN = 'unknown';
export class InstallationError extends Error {
constructor(message: string, readonly code: string) {
super(message);
}
}
function parseManifest(raw: string): TPromise<{ manifest: IExtensionManifest; metadata: IGalleryMetadata; }> {
return new TPromise((c, e) => {
@@ -49,7 +66,7 @@ function parseManifest(raw: string): TPromise<{ manifest: IExtensionManifest; me
});
}
function validate(zipPath: string): TPromise<IExtensionManifest> {
export function validateLocalExtension(zipPath: string): TPromise<IExtensionManifest> {
return buffer(zipPath, 'extension/package.json')
.then(buffer => parseManifest(buffer.toString('utf8')))
.then(({ manifest }) => TPromise.as(manifest));
@@ -75,7 +92,7 @@ function readManifest(extensionPath: string): TPromise<{ manifest: IExtensionMan
interface InstallableExtension {
zipPath: string;
id: string;
metadata: IGalleryMetadata;
metadata?: IGalleryMetadata;
current?: ILocalExtension;
}
@@ -86,6 +103,8 @@ export class ExtensionManagementService implements IExtensionManagementService {
private extensionsPath: string;
private obsoletePath: string;
private obsoleteFileLimiter: Limiter<void>;
private reportedExtensions: TPromise<IReportedExtension[]> | undefined;
private lastReportTimestamp = 0;
private disposables: IDisposable[] = [];
private _onInstallExtension = new Emitter<InstallExtensionEvent>();
@@ -103,93 +122,171 @@ export class ExtensionManagementService implements IExtensionManagementService {
constructor(
@IEnvironmentService private environmentService: IEnvironmentService,
@IChoiceService private choiceService: IChoiceService,
@IExtensionGalleryService private galleryService: IExtensionGalleryService
@IExtensionGalleryService private galleryService: IExtensionGalleryService,
@ILogService private logService: ILogService
) {
this.extensionsPath = environmentService.extensionsPath;
this.obsoletePath = path.join(this.extensionsPath, '.obsolete');
this.obsoleteFileLimiter = new Limiter(1);
}
private deleteExtensionsManifestCache(): void {
const cacheFolder = path.join(this.environmentService.userDataPath, MANIFEST_CACHE_FOLDER);
const cacheFile = path.join(cacheFolder, USER_MANIFEST_CACHE_FILE);
pfs.del(cacheFile).done(() => { }, () => { });
}
install(zipPath: string): TPromise<void> {
this.deleteExtensionsManifestCache();
zipPath = path.resolve(zipPath);
return validate(zipPath).then<void>(manifest => {
const identifier = { id: getLocalExtensionIdFromManifest(manifest) };
return this.isObsolete(identifier.id).then(isObsolete => {
if (isObsolete) {
return TPromise.wrapError(new Error(nls.localize('restartCodeLocal', "Please restart Code before reinstalling {0}.", manifest.displayName || manifest.name)));
}
this._onInstallExtension.fire({ identifier, zipPath });
// {{SQL CARBON EDIT}}
// Until there's a gallery for SQL Ops Studio, skip retrieving the metadata from the gallery
return this.installExtension({ zipPath, id: identifier.id, metadata: null })
.then(
local => this._onDidInstallExtension.fire({ identifier, zipPath, local }),
error => { this._onDidInstallExtension.fire({ identifier, zipPath, error }); return TPromise.wrapError(error); }
);
/*
return this.galleryService.query({ names: [getGalleryExtensionId(manifest.publisher, manifest.name)], pageSize: 1 })
.then(galleryResult => {
const galleryExtension = galleryResult.firstPage[0];
const metadata = galleryExtension ? <IGalleryMetadata>{ id: galleryExtension.identifier.uuid, publisherDisplayName: galleryExtension.publisherDisplayName, publisherId: galleryExtension.publisherId } : null;
return this.installExtension({ zipPath, id: identifier.id, metadata })
.then(
local => this._onDidInstallExtension.fire({ identifier, zipPath, local }),
error => { this._onDidInstallExtension.fire({ identifier, zipPath, error }); return TPromise.wrapError(error); }
);
return validateLocalExtension(zipPath)
.then(manifest => {
const identifier = { id: getLocalExtensionIdFromManifest(manifest) };
return this.isObsolete(identifier.id)
.then(isObsolete => {
if (isObsolete) {
return TPromise.wrapError(new Error(nls.localize('restartCodeLocal', "Please restart Code before reinstalling {0}.", manifest.displayName || manifest.name)));
}
return this.checkOutdated(manifest)
.then(validated => {
if (validated) {
this._onInstallExtension.fire({ identifier, zipPath });
// {{SQL CARBON EDIT}}
// Until there's a gallery for SQL Ops Studio, skip retrieving the metadata from the gallery
return this.installExtension({ zipPath, id: identifier.id, metadata: null })
.then(
local => this._onDidInstallExtension.fire({ identifier, zipPath, local }),
error => { this._onDidInstallExtension.fire({ identifier, zipPath, error }); return TPromise.wrapError(error); }
);
/*
return this.getMetadata(getGalleryExtensionId(manifest.publisher, manifest.name))
.then(
metadata => this.installFromZipPath(identifier, zipPath, metadata, manifest),
error => this.installFromZipPath(identifier, zipPath, null, manifest));
*/
}
return null;
});
});
*/
});
});
}
installFromGallery(extension: IGalleryExtension): TPromise<void> {
return this.prepareAndCollectExtensionsToInstall(extension)
.then(extensionsToInstall => this.downloadAndInstallExtensions(extensionsToInstall)
.then(local => this.onDidInstallExtensions(extensionsToInstall, local)));
private checkOutdated(manifest: IExtensionManifest): TPromise<boolean> {
const extensionIdentifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) };
return this.getInstalled()
.then(installedExtensions => {
const newer = installedExtensions.filter(local => areSameExtensions(extensionIdentifier, { id: getGalleryExtensionIdFromLocal(local) }) && semver.gt(local.manifest.version, manifest.version))[0];
if (newer) {
const message = nls.localize('installingOutdatedExtension', "A newer version of this extension is already installed. Would you like to override this with the older version?");
const options = [
nls.localize('override', "Override"),
nls.localize('cancel', "Cancel")
];
return this.choiceService.choose(Severity.Info, message, options, 1, true)
.then<boolean>(value => {
if (value === 0) {
return this.uninstall(newer, true).then(() => true);
}
return TPromise.wrapError(errors.canceled());
});
}
return true;
});
}
private prepareAndCollectExtensionsToInstall(extension: IGalleryExtension): TPromise<IGalleryExtension[]> {
this.onInstallExtensions([extension]);
return this.collectExtensionsToInstall(extension)
private installFromZipPath(identifier: IExtensionIdentifier, zipPath: string, metadata: IGalleryMetadata, manifest: IExtensionManifest): TPromise<void> {
return this.installExtension({ zipPath, id: identifier.id, metadata })
.then(local => {
if (this.galleryService.isEnabled() && local.manifest.extensionDependencies && local.manifest.extensionDependencies.length) {
return this.getDependenciesToInstall(local.manifest.extensionDependencies)
.then(dependenciesToInstall => this.downloadAndInstallExtensions(metadata ? dependenciesToInstall.filter(d => d.identifier.uuid !== metadata.id) : dependenciesToInstall))
.then(() => local, error => {
this.uninstallExtension(local.identifier);
return TPromise.wrapError(error);
});
}
return local;
})
.then(
extensionsToInstall => this.checkForObsolete(extensionsToInstall)
.then(
extensionsToInstall => {
if (extensionsToInstall.length > 1) {
this.onInstallExtensions(extensionsToInstall.slice(1));
}
return extensionsToInstall;
},
error => this.onDidInstallExtensions([extension], null, INSTALL_ERROR_OBSOLETE, error)
),
error => this.onDidInstallExtensions([extension], null, INSTALL_ERROR_GALLERY, error)
local => this._onDidInstallExtension.fire({ identifier, zipPath, local }),
error => { this._onDidInstallExtension.fire({ identifier, zipPath, error }); return TPromise.wrapError(error); }
);
}
private downloadAndInstallExtensions(extensions: IGalleryExtension[]): TPromise<ILocalExtension[]> {
return this.getInstalled(LocalExtensionType.User)
.then(installed => TPromise.join(extensions.map(extensionToInstall => this.downloadInstallableExtension(extensionToInstall, installed)))
.then(
installableExtensions => TPromise.join(installableExtensions.map(installableExtension => this.installExtension(installableExtension)))
.then(null, error => this.rollback(extensions).then(() => this.onDidInstallExtensions(extensions, null, INSTALL_ERROR_LOCAL, error))),
error => this.onDidInstallExtensions(extensions, null, INSTALL_ERROR_GALLERY, error)));
installFromGallery(extension: IGalleryExtension): TPromise<void> {
this.deleteExtensionsManifestCache();
this.onInstallExtensions([extension]);
return this.collectExtensionsToInstall(extension)
.then(
extensionsToInstall => {
if (extensionsToInstall.length > 1) {
this.onInstallExtensions(extensionsToInstall.slice(1));
}
return this.downloadAndInstallExtensions(extensionsToInstall)
.then(
locals => this.onDidInstallExtensions(extensionsToInstall, locals, []),
errors => this.onDidInstallExtensions(extensionsToInstall, [], errors));
},
error => this.onDidInstallExtensions([extension], [], [error]));
}
private collectExtensionsToInstall(extension: IGalleryExtension): TPromise<IGalleryExtension[]> {
return this.galleryService.loadCompatibleVersion(extension)
.then(extensionToInstall => this.galleryService.getAllDependencies(extension)
.then(allDependencies => this.filterDependenciesToInstall(extension, allDependencies))
.then(dependenciesToInstall => [extensionToInstall, ...dependenciesToInstall]));
.then(compatible => {
if (!compatible) {
return TPromise.wrapError<IGalleryExtension[]>(new InstallationError(nls.localize('notFoundCompatible', "Unable to install because, the extension '{0}' compatible with current version '{1}' of VS Code is not found.", extension.identifier.id, pkg.version), INSTALL_ERROR_INCOMPATIBLE));
}
return this.getDependenciesToInstall(compatible.properties.dependencies)
.then(
dependenciesToInstall => {
const extensionsToInstall = [compatible, ...dependenciesToInstall.filter(d => d.identifier.uuid !== compatible.identifier.uuid)];
return this.checkForObsolete(extensionsToInstall)
.then(
extensionsToInstall => extensionsToInstall,
error => TPromise.wrapError<IGalleryExtension[]>(new InstallationError(this.joinErrors(error).message, INSTALL_ERROR_OBSOLETE))
);
},
error => TPromise.wrapError<IGalleryExtension[]>(new InstallationError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY)));
},
error => TPromise.wrapError<IGalleryExtension[]>(new InstallationError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY)));
}
private downloadAndInstallExtensions(extensions: IGalleryExtension[]): TPromise<ILocalExtension[]> {
return this.getInstalled(LocalExtensionType.User)
.then(
installed => TPromise.join(extensions.map(extensionToInstall =>
this.downloadAndInstallExtension(extensionToInstall, installed)
)).then(null, errors => this.rollback(extensions).then(() => TPromise.wrapError(errors), () => TPromise.wrapError(errors))),
error => TPromise.wrapError<ILocalExtension[]>(new InstallationError(this.joinErrors(error).message, INSTALL_ERROR_LOCAL)));
}
private downloadAndInstallExtension(extensionToInstall: IGalleryExtension, installed: ILocalExtension[]): TPromise<ILocalExtension> {
return this.getExtensionsReport().then(report => {
if (getMaliciousExtensionsSet(report).has(extensionToInstall.identifier.id)) {
throw new Error(nls.localize('malicious extension', "Can't install extension since it was reported to be malicious."));
}
return this.downloadInstallableExtension(extensionToInstall, installed)
.then(installableExtension => this.installExtension(installableExtension).then(null, e => TPromise.wrapError(new InstallationError(this.joinErrors(e).message, INSTALL_ERROR_EXTRACTING))));
});
}
private checkForObsolete(extensionsToInstall: IGalleryExtension[]): TPromise<IGalleryExtension[]> {
return this.filterObsolete(...extensionsToInstall.map(i => getLocalExtensionIdFromGallery(i, i.version)))
.then(obsolete => obsolete.length ? TPromise.wrapError<IGalleryExtension[]>(new Error(nls.localize('restartCodeGallery', "Please restart Code before reinstalling."))) : extensionsToInstall);
.then(obsolete => {
if (obsolete.length) {
if (isMacintosh) {
return TPromise.wrapError<IGalleryExtension[]>(new Error(nls.localize('quitCode', "Unable to install because an obsolete instance of the extension is still running. Please Quit and Start VS Code before reinstalling.")));
}
return TPromise.wrapError<IGalleryExtension[]>(new Error(nls.localize('exitCode', "Unable to install because an obsolete instance of the extension is still running. Please Exit and Start VS Code before reinstalling.")));
}
return extensionsToInstall;
});
}
private downloadInstallableExtension(extension: IGalleryExtension, installed: ILocalExtension[]): TPromise<InstallableExtension> {
@@ -200,8 +297,24 @@ export class ExtensionManagementService implements IExtensionManagementService {
publisherId: extension.publisherId,
publisherDisplayName: extension.publisherDisplayName,
};
return this.galleryService.download(extension)
.then(zipPath => validate(zipPath).then(() => (<InstallableExtension>{ zipPath, id, metadata, current })));
return this.galleryService.loadCompatibleVersion(extension)
.then(
compatible => {
if (compatible) {
return this.galleryService.download(extension)
.then(
zipPath => validateLocalExtension(zipPath)
.then(
() => (<InstallableExtension>{ zipPath, id, metadata, current }),
error => TPromise.wrapError(new InstallationError(this.joinErrors(error).message, INSTALL_ERROR_VALIDATING))
),
error => TPromise.wrapError(new InstallationError(this.joinErrors(error).message, INSTALL_ERROR_DOWNLOADING)));
} else {
return TPromise.wrapError<InstallableExtension>(new InstallationError(nls.localize('notFoundCompatibleDependency', "Unable to install because, the depending extension '{0}' compatible with current version '{1}' of VS Code is not found.", extension.identifier.id, pkg.version), INSTALL_ERROR_INCOMPATIBLE));
}
},
error => TPromise.wrapError<InstallableExtension>(new InstallationError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY)));
}
private rollback(extensions: IGalleryExtension[]): TPromise<void> {
@@ -217,29 +330,33 @@ export class ExtensionManagementService implements IExtensionManagementService {
}
}
private onDidInstallExtensions(extensions: IGalleryExtension[], local: ILocalExtension[], errorCode?: string, error?: any): TPromise<any> {
private onDidInstallExtensions(extensions: IGalleryExtension[], locals: ILocalExtension[], errors: Error[]): TPromise<any> {
extensions.forEach((gallery, index) => {
const identifier = { id: getLocalExtensionIdFromGallery(gallery, gallery.version), uuid: gallery.identifier.uuid };
if (errorCode) {
this._onDidInstallExtension.fire({ identifier, gallery, error: errorCode });
const local = locals[index];
const error = errors[index];
if (local) {
this._onDidInstallExtension.fire({ identifier, gallery, local });
} else {
this._onDidInstallExtension.fire({ identifier, gallery, local: local[index] });
const errorCode = error && (<InstallationError>error).code ? (<InstallationError>error).code : INSTALL_ERROR_UNKNOWN;
this._onDidInstallExtension.fire({ identifier, gallery, error: errorCode });
}
});
return error ? TPromise.wrapError(Array.isArray(error) ? this.joinErrors(error) : error) : TPromise.as(null);
return errors.length ? TPromise.wrapError(this.joinErrors(errors)) : TPromise.as(null);
}
private filterDependenciesToInstall(extension: IGalleryExtension, dependencies: IGalleryExtension[]): TPromise<IGalleryExtension[]> {
return this.getInstalled()
.then(local => {
return dependencies.filter(d => {
if (extension.identifier.uuid === d.identifier.uuid) {
return false;
}
const extensionId = getLocalExtensionIdFromGallery(d, d.version);
return local.every(({ identifier }) => identifier.id !== extensionId);
});
});
private getDependenciesToInstall(dependencies: string[]): TPromise<IGalleryExtension[]> {
if (dependencies.length) {
return this.galleryService.loadAllDependencies(dependencies.map(id => (<IExtensionIdentifier>{ id })))
.then(allDependencies => this.getInstalled()
.then(local => {
return allDependencies.filter(d => {
const extensionId = getLocalExtensionIdFromGallery(d, d.version);
return local.every(({ identifier }) => identifier.id !== extensionId);
});
}));
}
return TPromise.as([]);
}
private filterOutUninstalled(extensions: IGalleryExtension[]): TPromise<ILocalExtension[]> {
@@ -278,6 +395,8 @@ export class ExtensionManagementService implements IExtensionManagementService {
}
uninstall(extension: ILocalExtension, force = false): TPromise<void> {
this.deleteExtensionsManifestCache();
return this.removeOutdatedExtensions()
.then(() =>
this.scanUserExtensions()
@@ -285,12 +404,14 @@ export class ExtensionManagementService implements IExtensionManagementService {
const promises = installed
.filter(e => e.manifest.publisher === extension.manifest.publisher && e.manifest.name === extension.manifest.name)
.map(e => this.checkForDependenciesAndUninstall(e, installed, force));
return TPromise.join(promises).then(null, error => TPromise.wrapError(Array.isArray(error) ? this.joinErrors(error) : error));
return TPromise.join(promises).then(null, error => TPromise.wrapError(this.joinErrors(error)));
}))
.then(() => { /* drop resolved value */ });
}
updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): TPromise<ILocalExtension> {
this.deleteExtensionsManifestCache();
local.metadata = metadata;
return this.saveMetadataForLocalExtension(local);
}
@@ -307,20 +428,27 @@ export class ExtensionManagementService implements IExtensionManagementService {
.then(() => local);
}
private getMetadata(extensionName: string): TPromise<IGalleryMetadata> {
return this.galleryService.query({ names: [extensionName], pageSize: 1 })
.then(galleryResult => {
const galleryExtension = galleryResult.firstPage[0];
return galleryExtension ? <IGalleryMetadata>{ id: galleryExtension.identifier.uuid, publisherDisplayName: galleryExtension.publisherDisplayName, publisherId: galleryExtension.publisherId } : null;
});
}
private checkForRename(currentExtension: ILocalExtension, newExtension: ILocalExtension): TPromise<void> {
// Check if the gallery id for current and new exensions are same, if not, remove the current one.
if (currentExtension && getGalleryExtensionIdFromLocal(currentExtension) !== getGalleryExtensionIdFromLocal(newExtension)) {
// return this.uninstallExtension(currentExtension.identifier);
return this.setObsolete(currentExtension.identifier.id);
}
return TPromise.as(null);
}
private joinErrors(errors: (Error | string)[]): Error {
private joinErrors(errorOrErrors: (Error | string) | ((Error | string)[])): Error {
const errors = Array.isArray(errorOrErrors) ? errorOrErrors : [errorOrErrors];
if (errors.length === 1) {
return errors[0] instanceof Error ? <Error>errors[0] : new Error(<string>errors[0]);
}
return errors.reduce<Error>((previousValue: Error, currentValue: Error | string) => {
return new Error(`${previousValue.message}${previousValue.message ? ',' : ''}${currentValue instanceof Error ? currentValue.message : currentValue}`);
}, new Error(''));
@@ -537,8 +665,9 @@ export class ExtensionManagementService implements IExtensionManagementService {
removeDeprecatedExtensions(): TPromise<any> {
return TPromise.join([
this.removeOutdatedExtensions(),
this.removeObsoleteExtensions()
// Remove obsolte extensions first to avoid removing installed older extension. See #38609.
this.removeObsoleteExtensions(),
this.removeOutdatedExtensions()
]);
}
@@ -621,6 +750,30 @@ export class ExtensionManagementService implements IExtensionManagementService {
});
}
getExtensionsReport(): TPromise<IReportedExtension[]> {
const now = new Date().getTime();
if (!this.reportedExtensions || now - this.lastReportTimestamp > 1000 * 60 * 5) { // 5 minute cache freshness
this.reportedExtensions = this.updateReportCache();
this.lastReportTimestamp = now;
}
return this.reportedExtensions;
}
private updateReportCache(): TPromise<IReportedExtension[]> {
this.logService.trace('ExtensionManagementService.refreshReportedCache');
return this.galleryService.getExtensionsReport()
.then(result => {
this.logService.trace(`ExtensionManagementService.refreshReportedCache - got ${result.length} reported extensions from service`);
return result;
}, err => {
this.logService.trace('ExtensionManagementService.refreshReportedCache - failed to get extension report');
return [];
});
}
dispose() {
this.disposables = dispose(this.disposables);
}
@@ -636,4 +789,4 @@ export function getLocalExtensionIdFromManifest(manifest: IExtensionManifest): s
function getLocalExtensionId(id: string, version: string): string {
return `${id}-${version}`;
}
}

View File

@@ -0,0 +1,23 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as semver from 'semver';
import { adoptToGalleryExtensionId, LOCAL_EXTENSION_ID_REGEX } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
export function getIdAndVersionFromLocalExtensionId(localExtensionId: string): { id: string, version: string } {
const matches = LOCAL_EXTENSION_ID_REGEX.exec(localExtensionId);
if (matches && matches[1] && matches[2]) {
const version = semver.valid(matches[2]);
if (version) {
return { id: adoptToGalleryExtensionId(matches[1]), version };
}
}
return {
id: adoptToGalleryExtensionId(localExtensionId),
version: null
};
}

View File

@@ -6,7 +6,7 @@
import * as assert from 'assert';
import * as sinon from 'sinon';
import { IExtensionManagementService, IExtensionEnablementService, DidUninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementService, IExtensionEnablementService, DidUninstallExtensionEvent, EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { Emitter } from 'vs/base/common/event';
@@ -14,6 +14,7 @@ import { StorageService, InMemoryLocalStorage } from 'vs/platform/storage/common
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { TPromise } from 'vs/base/common/winjs.base';
function storageService(instantiationService: TestInstantiationService): IStorageService {
let service = instantiationService.get(IStorageService);
@@ -33,13 +34,12 @@ function storageService(instantiationService: TestInstantiationService): IStorag
export class TestExtensionEnablementService extends ExtensionEnablementService {
constructor(instantiationService: TestInstantiationService) {
super(storageService(instantiationService), instantiationService.get(IWorkspaceContextService),
instantiationService.get(IEnvironmentService) || instantiationService.stub(IEnvironmentService, <IEnvironmentService>{}),
instantiationService.get(IEnvironmentService) || instantiationService.stub(IEnvironmentService, {} as IEnvironmentService),
instantiationService.get(IExtensionManagementService) || instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: new Emitter() }));
}
public reset(): void {
this.getGloballyDisabledExtensions().forEach(d => this.setEnablement(d, true));
this.getWorkspaceDisabledExtensions().forEach(d => this.setEnablement(d, true, true));
public reset(): TPromise<void> {
return this.getDisabledExtensions().then(extensions => extensions.forEach(d => this.setEnablement(d, EnablementState.Enabled)));
}
}
@@ -60,216 +60,268 @@ suite('ExtensionEnablementService Test', () => {
(<ExtensionEnablementService>testObject).dispose();
});
test('test when no extensions are disabled globally', () => {
assert.deepEqual([], testObject.getGloballyDisabledExtensions());
test('test when no extensions are disabled', () => {
return testObject.getDisabledExtensions().then(extensions => assert.deepEqual([], extensions));
});
test('test when no extensions are disabled for workspace', () => {
assert.deepEqual([], testObject.getWorkspaceDisabledExtensions());
});
test('test when no extensions are disabled for workspace when there is no workspace', (done) => {
testObject.setEnablement({ id: 'pub.a' }, false, true)
test('test when no extensions are disabled for workspace when there is no workspace', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(() => {
instantiationService.stub(IWorkspaceContextService, 'getWorkbenchState', WorkbenchState.EMPTY);
assert.deepEqual([], testObject.getWorkspaceDisabledExtensions());
})
.then(done, done);
return testObject.getDisabledExtensions().then(extensions => assert.deepEqual([], extensions));
});
});
test('test disable an extension globally', (done) => {
testObject.setEnablement({ id: 'pub.a' }, false)
.then(() => assert.deepEqual([{ id: 'pub.a' }], testObject.getGloballyDisabledExtensions()))
.then(done, done);
test('test disable an extension globally', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled)
.then(() => testObject.getDisabledExtensions())
.then(extensions => assert.deepEqual([{ id: 'pub.a' }], extensions));
});
test('test disable an extension globally should return truthy promise', (done) => {
testObject.setEnablement({ id: 'pub.a' }, false)
.then(value => assert.ok(value))
.then(done, done);
test('test disable an extension globally should return truthy promise', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled)
.then(value => assert.ok(value));
});
test('test disable an extension globally triggers the change event', (done) => {
test('test disable an extension globally triggers the change event', () => {
const target = sinon.spy();
testObject.onEnablementChanged(target);
testObject.setEnablement({ id: 'pub.a' }, false)
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.a' })))
.then(done, done);
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled)
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.a' })));
});
test('test disable an extension globally again should return a falsy promise', (done) => {
testObject.setEnablement({ id: 'pub.a' }, false)
.then(() => testObject.setEnablement({ id: 'pub.a' }, false))
.then(value => assert.ok(!value))
.then(done, done);
test('test disable an extension globally again should return a falsy promise', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled))
.then(value => assert.ok(!value));
});
test('test disable an extension for workspace', (done) => {
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(() => assert.deepEqual([{ id: 'pub.a' }], testObject.getWorkspaceDisabledExtensions()))
.then(done, done);
test('test state of globally disabled extension', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled)
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Disabled));
});
test('test disable an extension for workspace returns a truthy promise', (done) => {
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(value => assert.ok(value))
.then(done, done);
test('test state of globally enabled extension', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.Enabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Enabled));
});
test('test disable an extension for workspace again should return a falsy promise', (done) => {
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(() => testObject.setEnablement({ id: 'pub.a' }, false, true))
.then(value => assert.ok(!value))
.then(done, done);
test('test disable an extension for workspace', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(() => testObject.getDisabledExtensions())
.then(extensions => assert.deepEqual([{ id: 'pub.a' }], extensions));
});
test('test disable an extension for workspace and then globally', (done) => {
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(() => testObject.setEnablement({ id: 'pub.a' }, false))
.then(() => {
assert.deepEqual([{ id: 'pub.a' }], testObject.getWorkspaceDisabledExtensions());
assert.deepEqual([{ id: 'pub.a' }], testObject.getGloballyDisabledExtensions());
})
.then(done, done);
test('test disable an extension for workspace returns a truthy promise', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(value => assert.ok(value));
});
test('test disable an extension for workspace and then globally return a truthy promise', (done) => {
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(() => testObject.setEnablement({ id: 'pub.a' }, false))
.then(value => assert.ok(value))
.then(done, done);
test('test disable an extension for workspace again should return a falsy promise', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled))
.then(value => assert.ok(!value));
});
test('test disable an extension for workspace and then globally triggers the change event', (done) => {
test('test state of workspace disabled extension', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.WorkspaceDisabled));
});
test('test state of workspace and globally disabled extension', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.WorkspaceDisabled));
});
test('test state of workspace enabled extension', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceEnabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.WorkspaceEnabled));
});
test('test state of globally disabled and workspace enabled extension', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled))
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceEnabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.WorkspaceEnabled));
});
test('test state of an extension when disabled for workspace from workspace enabled', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceEnabled))
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.WorkspaceDisabled));
});
test('test state of an extension when disabled globally from workspace enabled', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceEnabled))
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Disabled));
});
test('test state of an extension when disabled globally from workspace disabled', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Disabled));
});
test('test state of an extension when enabled globally from workspace enabled', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceEnabled))
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.Enabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Enabled));
});
test('test state of an extension when enabled globally from workspace disabled', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.Enabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Enabled));
});
test('test disable an extension for workspace and then globally', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled))
.then(() => testObject.getDisabledExtensions())
.then(extensions => assert.deepEqual([{ id: 'pub.a' }], extensions));
});
test('test disable an extension for workspace and then globally return a truthy promise', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled))
.then(value => assert.ok(value));
});
test('test disable an extension for workspace and then globally trigger the change event', () => {
const target = sinon.spy();
testObject.setEnablement({ id: 'pub.a' }, false, true)
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(() => testObject.onEnablementChanged(target))
.then(() => testObject.setEnablement({ id: 'pub.a' }, false))
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.a' })))
.then(done, done);
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled))
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.a' })));
});
test('test disable an extension globally and then for workspace', (done) => {
testObject.setEnablement({ id: 'pub.a' }, false)
.then(() => testObject.setEnablement({ id: 'pub.a' }, false, true))
.then(() => {
assert.deepEqual([{ id: 'pub.a' }], testObject.getWorkspaceDisabledExtensions());
assert.deepEqual([{ id: 'pub.a' }], testObject.getGloballyDisabledExtensions());
})
.then(done, done);
test('test disable an extension globally and then for workspace', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled))
.then(() => testObject.getDisabledExtensions())
.then(extensions => assert.deepEqual([{ id: 'pub.a' }], extensions));
});
test('test disable an extension globally and then for workspace return a truthy promise', (done) => {
testObject.setEnablement({ id: 'pub.a' }, false)
.then(() => testObject.setEnablement({ id: 'pub.a' }, false, true))
.then(value => assert.ok(value))
.then(done, done);
test('test disable an extension globally and then for workspace return a truthy promise', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled))
.then(value => assert.ok(value));
});
test('test disable an extension globally and then for workspace triggers the change event', (done) => {
test('test disable an extension globally and then for workspace triggers the change event', () => {
const target = sinon.spy();
testObject.setEnablement({ id: 'pub.a' }, false)
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled)
.then(() => testObject.onEnablementChanged(target))
.then(() => testObject.setEnablement({ id: 'pub.a' }, false, true))
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.a' })))
.then(done, done);
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled))
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.a' })));
});
test('test disable an extension for workspace when there is no workspace throws error', (done) => {
instantiationService.stub(IWorkspaceContextService, 'getWorkbenchState', WorkbenchState.EMPTY);
testObject.setEnablement({ id: 'pub.a' }, false, true)
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(() => assert.fail('should throw an error'), error => assert.ok(error))
.then(done, done);
});
test('test enable an extension globally', (done) => {
testObject.setEnablement({ id: 'pub.a' }, false)
.then(() => testObject.setEnablement({ id: 'pub.a' }, true))
.then(() => assert.deepEqual([], testObject.getGloballyDisabledExtensions()))
.then(done, done);
test('test enable an extension globally', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.Enabled))
.then(() => testObject.getDisabledExtensions())
.then(extensions => assert.deepEqual([], extensions));
});
test('test enable an extension globally return truthy promise', (done) => {
testObject.setEnablement({ id: 'pub.a' }, false)
.then(() => testObject.setEnablement({ id: 'pub.a' }, true))
testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.Enabled))
.then(value => assert.ok(value))
.then(done, done);
});
test('test enable an extension globally triggers change event', (done) => {
const target = sinon.spy();
testObject.setEnablement({ id: 'pub.a' }, false)
testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled)
.then(() => testObject.onEnablementChanged(target))
.then(() => testObject.setEnablement({ id: 'pub.a' }, true))
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.Enabled))
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.a' })))
.then(done, done);
});
test('test enable an extension globally when already enabled return falsy promise', (done) => {
testObject.setEnablement({ id: 'pub.a' }, true)
testObject.setEnablement({ id: 'pub.a' }, EnablementState.Enabled)
.then(value => assert.ok(!value))
.then(done, done);
});
test('test enable an extension for workspace', (done) => {
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(() => testObject.setEnablement({ id: 'pub.a' }, true, true))
.then(() => assert.deepEqual([], testObject.getWorkspaceDisabledExtensions()))
.then(done, done);
test('test enable an extension for workspace', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceEnabled))
.then(() => testObject.getDisabledExtensions())
.then(extensions => assert.deepEqual([], extensions));
});
test('test enable an extension for workspace return truthy promise', (done) => {
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(() => testObject.setEnablement({ id: 'pub.a' }, true, true))
.then(value => assert.ok(value))
.then(done, done);
test('test enable an extension for workspace return truthy promise', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceEnabled))
.then(value => assert.ok(value));
});
test('test enable an extension for workspace triggers change event', (done) => {
test('test enable an extension for workspace triggers change event', () => {
const target = sinon.spy();
testObject.setEnablement({ id: 'pub.b' }, false, true)
return testObject.setEnablement({ id: 'pub.b' }, EnablementState.WorkspaceDisabled)
.then(() => testObject.onEnablementChanged(target))
.then(() => testObject.setEnablement({ id: 'pub.b' }, true, true))
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.b' })))
.then(done, done);
.then(() => testObject.setEnablement({ id: 'pub.b' }, EnablementState.WorkspaceEnabled))
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.b' })));
});
test('test enable an extension for workspace when already enabled return falsy promise', (done) => {
testObject.setEnablement({ id: 'pub.a' }, true, true)
.then(value => assert.ok(!value))
.then(done, done);
test('test enable an extension for workspace when already enabled return truthy promise', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceEnabled)
.then(value => assert.ok(value));
});
test('test enable an extension for workspace when disabled in workspace and gloablly', (done) => {
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(() => testObject.setEnablement({ id: 'pub.a' }, false))
.then(() => testObject.setEnablement({ id: 'pub.a' }, true, true))
.then(() => {
assert.deepEqual([{ id: 'pub.a' }], testObject.getGloballyDisabledExtensions());
assert.deepEqual([], testObject.getWorkspaceDisabledExtensions());
})
.then(done, done);
test('test enable an extension for workspace when disabled in workspace and gloablly', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled))
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceEnabled))
.then(() => testObject.getDisabledExtensions())
.then(extensions => assert.deepEqual([], extensions));
});
test('test enable an extension globally when disabled in workspace and gloablly', (done) => {
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(() => testObject.setEnablement({ id: 'pub.a' }, false))
.then(() => testObject.setEnablement({ id: 'pub.a' }, true))
.then(() => {
assert.deepEqual([{ id: 'pub.a' }], testObject.getWorkspaceDisabledExtensions());
assert.deepEqual([], testObject.getGloballyDisabledExtensions());
})
.then(done, done);
test('test enable an extension globally when disabled in workspace and gloablly', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled))
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.Enabled))
.then(() => testObject.getDisabledExtensions())
.then(extensions => assert.deepEqual([], extensions));
});
test('test remove an extension from disablement list when uninstalled', (done) => {
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(() => testObject.setEnablement({ id: 'pub.a' }, false))
test('test remove an extension from disablement list when uninstalled', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled))
.then(() => didUninstallEvent.fire({ identifier: { id: 'pub.a-1.0.0' } }))
.then(() => {
assert.deepEqual([], testObject.getWorkspaceDisabledExtensions());
assert.deepEqual([], testObject.getGloballyDisabledExtensions());
})
.then(done, done);
.then(() => testObject.getDisabledExtensions())
.then(extensions => assert.deepEqual([], extensions));
});
test('test isEnabled return false extension is disabled globally', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.Disabled)
.then(() => assert.ok(!testObject.isEnabled({ id: 'pub.a' })));
});
test('test isEnabled return false extension is disabled in workspace', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(() => assert.ok(!testObject.isEnabled({ id: 'pub.a' })));
});
test('test isEnabled return true extension is not disabled', () => {
return testObject.setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement({ id: 'pub.c' }, EnablementState.Disabled))
.then(() => assert.ok(testObject.isEnabled({ id: 'pub.b' })));
});
});

View File

@@ -0,0 +1,51 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import * as os from 'os';
import extfs = require('vs/base/node/extfs');
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { parseArgs } from 'vs/platform/environment/node/argv';
import { getRandomTestPath } from 'vs/workbench/test/workbenchTestServices';
import { join } from 'path';
import { mkdirp } from 'vs/base/node/pfs';
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/node/extensionGalleryService';
import { isUUID } from 'vs/base/common/uuid';
suite('Extension Gallery Service', () => {
const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'extensiongalleryservice');
const marketplaceHome = join(parentDir, 'Marketplace');
setup(done => {
// Delete any existing backups completely and then re-create it.
extfs.del(marketplaceHome, os.tmpdir(), () => {
mkdirp(marketplaceHome).then(() => {
done();
});
});
});
teardown(done => {
extfs.del(marketplaceHome, os.tmpdir(), done);
});
test('marketplace machine id', done => {
const args = ['--user-data-dir', marketplaceHome];
const environmentService = new EnvironmentService(parseArgs(args), process.execPath);
return resolveMarketplaceHeaders(environmentService).then(headers => {
assert.ok(isUUID(headers['X-Market-User-Id']));
return resolveMarketplaceHeaders(environmentService).then(headers2 => {
assert.equal(headers['X-Market-User-Id'], headers2['X-Market-User-Id']);
done();
});
});
});
});

View File

@@ -8,6 +8,7 @@ import Severity from 'vs/base/common/severity';
import { TPromise } from 'vs/base/common/winjs.base';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionPoint } from 'vs/platform/extensions/common/extensionsRegistry';
import Event from 'vs/base/common/event';
export interface IExtensionDescription {
readonly id: string;
@@ -26,9 +27,16 @@ export interface IExtensionDescription {
readonly main?: string;
readonly contributes?: { [point: string]: any; };
readonly keywords?: string[];
readonly repository?: {
url: string;
};
enableProposedApi?: boolean;
}
export const MANIFEST_CACHE_FOLDER = 'CachedExtensions';
export const USER_MANIFEST_CACHE_FILE = 'user';
export const BUILTIN_MANIFEST_CACHE_FILE = 'builtin';
export const IExtensionService = createDecorator<IExtensionService>('extensionService');
export interface IMessage {
@@ -41,19 +49,63 @@ export interface IMessage {
export interface IExtensionsStatus {
messages: IMessage[];
activationTimes: ActivationTimes;
runtimeErrors: Error[];
}
export class ActivationTimes {
public readonly startup: boolean;
public readonly codeLoadingTime: number;
public readonly activateCallTime: number;
public readonly activateResolvedTime: number;
/**
* e.g.
* ```
* {
* startTime: 1511954813493000,
* endTime: 1511954835590000,
* deltas: [ 100, 1500, 123456, 1500, 100000 ],
* ids: [ 'idle', 'self', 'extension1', 'self', 'idle' ]
* }
* ```
*/
export interface IExtensionHostProfile {
/**
* Profiling start timestamp in microseconds.
*/
startTime: number;
/**
* Profiling end timestamp in microseconds.
*/
endTime: number;
/**
* Duration of segment in microseconds.
*/
deltas: number[];
/**
* Segment identifier: extension id or one of the four known strings.
*/
ids: ProfileSegmentId[];
constructor(startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number) {
this.startup = startup;
this.codeLoadingTime = codeLoadingTime;
this.activateCallTime = activateCallTime;
this.activateResolvedTime = activateResolvedTime;
/**
* Get the information as a .cpuprofile.
*/
data: object;
/**
* Get the aggregated time per segmentId
*/
getAggregatedTimes(): Map<ProfileSegmentId, number>;
}
/**
* Extension id or one of the four known program states.
*/
export type ProfileSegmentId = string | 'idle' | 'program' | 'gc' | 'self';
export class ActivationTimes {
constructor(
public readonly startup: boolean,
public readonly codeLoadingTime: number,
public readonly activateCallTime: number,
public readonly activateResolvedTime: number,
public readonly activationEvent: string
) {
}
}
@@ -67,18 +119,40 @@ export class ExtensionPointContribution<T> {
}
}
export interface IExtensionHostInformation {
inspectPort: number;
}
export interface IExtensionService {
_serviceBrand: any;
/**
* TODO@Ben: Delete this and use `whenInstalledExtensionsRegistered`
* An event emitted when extensions are registered after their extension points got handled.
*
* This event will also fire on startup to signal the installed extensions.
*
* @returns the extensions that got registered
*/
onDidRegisterExtensions: Event<IExtensionDescription[]>;
/**
* @event
* Fired when extensions status changes.
* The event contains the ids of the extensions that have changed.
*/
onDidChangeExtensionsStatus: Event<string[]>;
/**
* Send an activation event and activate interested extensions.
*/
activateByEvent(activationEvent: string): TPromise<void>;
/**
* Block on this signal any interactions with extensions.
* An promise that resolves when the installed extensions are registered after
* their extension points got handled.
*/
onReady(): TPromise<boolean>;
whenInstalledExtensionsRegistered(): TPromise<boolean>;
/**
* Return all registered extensions
@@ -96,9 +170,9 @@ export interface IExtensionService {
getExtensionsStatus(): { [id: string]: IExtensionsStatus };
/**
* Get information about extension activation times.
* Begin an extension host process profile session.
*/
getExtensionsActivationTimes(): { [id: string]: ActivationTimes; };
startExtensionHostProfile(): TPromise<ProfileSession>;
/**
* Restarts the extension host.
@@ -114,4 +188,13 @@ export interface IExtensionService {
* Stops the extension host.
*/
stopExtensionHost(): void;
/**
*
*/
getExtensionHostInformation(): IExtensionHostInformation;
}
export interface ProfileSession {
stop(): TPromise<IExtensionHostProfile>;
}

View File

@@ -11,6 +11,7 @@ import Severity from 'vs/base/common/severity';
import { IMessage, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/common/extensionManagement';
const hasOwnProperty = Object.hasOwnProperty;
const schemaRegistry = <IJSONContributionRegistry>Registry.as(Extensions.JSONContribution);
@@ -198,6 +199,16 @@ const schema: IJSONSchema = {
description: nls.localize('vscode.extension.activationEvents.onDebug', 'An activation event emitted whenever a user is about to start debugging or about to setup debug configurations.'),
body: 'onDebug'
},
{
label: 'onDebugInitialConfigurations',
description: nls.localize('vscode.extension.activationEvents.onDebugInitialConfigurations', 'An activation event emitted whenever a "launch.json" needs to be created (and all provideDebugConfigurations methods need to be called).'),
body: 'onDebugInitialConfigurations'
},
{
label: 'onDebugResolve',
description: nls.localize('vscode.extension.activationEvents.onDebugResolve', 'An activation event emitted whenever a debug session with the specific type is about to be launched (and a corresponding resolveDebugConfiguration method needs to be called).'),
body: 'onDebugResolve:${6:type}'
},
{
label: 'workspaceContains',
description: nls.localize('vscode.extension.activationEvents.workspaceContains', 'An activation event emitted whenever a folder is opened that contains at least a file matching the specified glob pattern.'),
@@ -243,7 +254,8 @@ const schema: IJSONSchema = {
type: 'array',
uniqueItems: true,
items: {
type: 'string'
type: 'string',
pattern: EXTENSION_IDENTIFIER_PATTERN
}
},
scripts: {

View File

@@ -8,7 +8,6 @@ import { TPromise } from 'vs/base/common/winjs.base';
import paths = require('vs/base/common/paths');
import URI from 'vs/base/common/uri';
import glob = require('vs/base/common/glob');
import events = require('vs/base/common/events');
import { isLinux } from 'vs/base/common/platform';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import Event from 'vs/base/common/event';
@@ -245,12 +244,11 @@ export interface IFileChange {
resource: URI;
}
export class FileChangesEvent extends events.Event {
export class FileChangesEvent {
private _changes: IFileChange[];
constructor(changes: IFileChange[]) {
super();
this._changes = changes;
}
@@ -449,6 +447,14 @@ export interface IContent extends IBaseStat {
encoding: string;
}
// this should eventually replace IContent such
// that we have a clear separation between content
// and metadata (TODO@Joh, TODO@Ben)
export interface IContentData {
encoding: string;
stream: IStringStream;
}
/**
* A Stream emitting strings.
*/
@@ -567,12 +573,6 @@ export enum FileOperationResult {
FILE_INVALID_PATH
}
// See https://github.com/Microsoft/vscode/issues/30180
const WIN32_MAX_FILE_SIZE = 300 * 1024 * 1024; // 300 MB
const GENERAL_MAX_FILE_SIZE = 16 * 1024 * 1024 * 1024; // 16 GB
export const MAX_FILE_SIZE = (typeof process === 'object' ? (process.arch === 'ia32' ? WIN32_MAX_FILE_SIZE : GENERAL_MAX_FILE_SIZE) : WIN32_MAX_FILE_SIZE);
export const AutoSaveConfiguration = {
OFF: 'off',
AFTER_DELAY: 'afterDelay',

View File

@@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// See https://github.com/Microsoft/vscode/issues/30180
const WIN32_MAX_FILE_SIZE = 300 * 1024 * 1024; // 300 MB
const GENERAL_MAX_FILE_SIZE = 16 * 1024 * 1024 * 1024; // 16 GB
export const MAX_FILE_SIZE = process.arch === 'ia32' ? WIN32_MAX_FILE_SIZE : GENERAL_MAX_FILE_SIZE;

View File

@@ -9,10 +9,10 @@ import * as path from 'path';
import * as nls from 'vs/nls';
import * as arrays from 'vs/base/common/arrays';
import { trim } from 'vs/base/common/strings';
import { IStorageService } from 'vs/platform/storage/node/storage';
import { IStateService } from 'vs/platform/state/common/state';
import { app } from 'electron';
import { ILogService } from 'vs/platform/log/common/log';
import { getPathLabel } from 'vs/base/common/labels';
import { getPathLabel, getBaseLabel } from 'vs/base/common/labels';
import { IPath } from 'vs/platform/windows/common/windows';
import CommonEvent, { Emitter } from 'vs/base/common/event';
import { isWindows, isMacintosh, isLinux } from 'vs/base/common/platform';
@@ -20,6 +20,7 @@ import { IWorkspaceIdentifier, IWorkspacesMainService, getWorkspaceLabel, ISingl
import { IHistoryMainService, IRecentlyOpened } from 'vs/platform/history/common/history';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { isEqual } from 'vs/base/common/paths';
import { RunOnceScheduler } from 'vs/base/common/async';
export interface ILegacyRecentlyOpened extends IRecentlyOpened {
folders: string[]; // TODO@Ben migration
@@ -27,26 +28,31 @@ export interface ILegacyRecentlyOpened extends IRecentlyOpened {
export class HistoryMainService implements IHistoryMainService {
private static MAX_TOTAL_RECENT_ENTRIES = 100;
private static readonly MAX_TOTAL_RECENT_ENTRIES = 100;
private static readonly MAX_MACOS_DOCK_RECENT_ENTRIES = 10;
private static recentlyOpenedStorageKey = 'openedPathsList';
private static readonly recentlyOpenedStorageKey = 'openedPathsList';
_serviceBrand: any;
private _onRecentlyOpenedChange = new Emitter<void>();
onRecentlyOpenedChange: CommonEvent<void> = this._onRecentlyOpenedChange.event;
private macOSRecentDocumentsUpdater: RunOnceScheduler;
constructor(
@IStorageService private storageService: IStorageService,
@IStateService private stateService: IStateService,
@ILogService private logService: ILogService,
@IWorkspacesMainService private workspacesService: IWorkspacesMainService,
@IWorkspacesMainService private workspacesMainService: IWorkspacesMainService,
@IEnvironmentService private environmentService: IEnvironmentService,
) {
this.macOSRecentDocumentsUpdater = new RunOnceScheduler(() => this.updateMacOSRecentDocuments(), 800);
this.registerListeners();
}
private registerListeners(): void {
this.workspacesService.onWorkspaceSaved(e => this.onWorkspaceSaved(e));
this.workspacesMainService.onWorkspaceSaved(e => this.onWorkspaceSaved(e));
}
private onWorkspaceSaved(e: IWorkspaceSavedEvent): void {
@@ -61,7 +67,7 @@ export class HistoryMainService implements IHistoryMainService {
// Workspaces
workspaces.forEach(workspace => {
const isUntitledWorkspace = !isSingleFolderWorkspaceIdentifier(workspace) && this.workspacesService.isUntitledWorkspace(workspace);
const isUntitledWorkspace = !isSingleFolderWorkspaceIdentifier(workspace) && this.workspacesMainService.isUntitledWorkspace(workspace);
if (isUntitledWorkspace) {
return; // only store saved workspaces
}
@@ -69,10 +75,8 @@ export class HistoryMainService implements IHistoryMainService {
mru.workspaces.unshift(workspace);
mru.workspaces = arrays.distinct(mru.workspaces, workspace => this.distinctFn(workspace));
// Add to recent documents (macOS only, Windows can show workspaces separately)
if (isMacintosh) {
app.addRecentDocument(isSingleFolderWorkspaceIdentifier(workspace) ? workspace : workspace.configPath);
}
// We do not add to recent documents here because on Windows we do this from a custom
// JumpList and on macOS we fill the recent documents in one go from all our data later.
});
// Files
@@ -80,8 +84,8 @@ export class HistoryMainService implements IHistoryMainService {
mru.files.unshift(path);
mru.files = arrays.distinct(mru.files, file => this.distinctFn(file));
// Add to recent documents (Windows/macOS only)
if (isMacintosh || isWindows) {
// Add to recent documents (Windows only, macOS later)
if (isWindows) {
app.addRecentDocument(path);
}
});
@@ -92,6 +96,11 @@ export class HistoryMainService implements IHistoryMainService {
this.saveRecentlyOpened(mru);
this._onRecentlyOpenedChange.fire();
// Schedule update to recent documents on macOS dock
if (isMacintosh) {
this.macOSRecentDocumentsUpdater.schedule();
}
}
}
@@ -119,6 +128,41 @@ export class HistoryMainService implements IHistoryMainService {
if (update) {
this.saveRecentlyOpened(mru);
this._onRecentlyOpenedChange.fire();
// Schedule update to recent documents on macOS dock
if (isMacintosh) {
this.macOSRecentDocumentsUpdater.schedule();
}
}
}
private updateMacOSRecentDocuments(): void {
if (!isMacintosh) {
return;
}
// macOS recent documents in the dock are behaving strangely. the entries seem to get
// out of sync quickly over time. the attempted fix is to always set the list fresh
// from our MRU history data. So we clear the documents first and then set the documents
// again.
app.clearRecentDocuments();
const mru = this.getRecentlyOpened();
let maxEntries = HistoryMainService.MAX_MACOS_DOCK_RECENT_ENTRIES;
// Take up to maxEntries/2 workspaces
for (let i = 0; i < mru.workspaces.length && i < HistoryMainService.MAX_MACOS_DOCK_RECENT_ENTRIES / 2; i++) {
const workspace = mru.workspaces[i];
app.addRecentDocument(isSingleFolderWorkspaceIdentifier(workspace) ? workspace : workspace.configPath);
maxEntries--;
}
// Take up to maxEntries files
for (let i = 0; i < mru.files.length && i < maxEntries; i++) {
const file = mru.files[i];
app.addRecentDocument(file);
}
}
@@ -135,7 +179,7 @@ export class HistoryMainService implements IHistoryMainService {
let files: string[];
// Get from storage
const storedRecents = this.storageService.getItem<IRecentlyOpened>(HistoryMainService.recentlyOpenedStorageKey) as ILegacyRecentlyOpened;
const storedRecents = this.stateService.getItem<IRecentlyOpened>(HistoryMainService.recentlyOpenedStorageKey) as ILegacyRecentlyOpened;
if (storedRecents) {
workspaces = storedRecents.workspaces || storedRecents.folders || [];
files = storedRecents.files || [];
@@ -159,7 +203,7 @@ export class HistoryMainService implements IHistoryMainService {
files = arrays.distinct(files, file => this.distinctFn(file));
// Hide untitled workspaces
workspaces = workspaces.filter(workspace => isSingleFolderWorkspaceIdentifier(workspace) || !this.workspacesService.isUntitledWorkspace(workspace));
workspaces = workspaces.filter(workspace => isSingleFolderWorkspaceIdentifier(workspace) || !this.workspacesMainService.isUntitledWorkspace(workspace));
return { workspaces, files };
}
@@ -173,7 +217,7 @@ export class HistoryMainService implements IHistoryMainService {
}
private saveRecentlyOpened(recent: IRecentlyOpened): void {
this.storageService.setItem(HistoryMainService.recentlyOpenedStorageKey, recent);
this.stateService.setItem(HistoryMainService.recentlyOpenedStorageKey, recent);
}
public updateWindowsJumpList(): void {
@@ -213,8 +257,8 @@ export class HistoryMainService implements IHistoryMainService {
type: 'custom',
name: nls.localize('recentFolders', "Recent Workspaces"),
items: this.getRecentlyOpened().workspaces.slice(0, 7 /* limit number of entries here */).map(workspace => {
const title = isSingleFolderWorkspaceIdentifier(workspace) ? path.basename(workspace) : getWorkspaceLabel(workspace, this.environmentService);
const description = isSingleFolderWorkspaceIdentifier(workspace) ? nls.localize('folderDesc', "{0} {1}", path.basename(workspace), getPathLabel(path.dirname(workspace))) : nls.localize('codeWorkspace', "Code Workspace");
const title = isSingleFolderWorkspaceIdentifier(workspace) ? getBaseLabel(workspace) : getWorkspaceLabel(workspace, this.environmentService);
const description = isSingleFolderWorkspaceIdentifier(workspace) ? nls.localize('folderDesc', "{0} {1}", getBaseLabel(workspace), getPathLabel(path.dirname(workspace))) : nls.localize('codeWorkspace', "Code Workspace");
return <Electron.JumpListItem>{
type: 'task',
@@ -237,7 +281,7 @@ export class HistoryMainService implements IHistoryMainService {
try {
app.setJumpList(jumpList);
} catch (error) {
this.logService.log('#setJumpList', error); // since setJumpList is relatively new API, make sure to guard for errors
this.logService.warn('#setJumpList', error); // since setJumpList is relatively new API, make sure to guard for errors
}
}
}

View File

@@ -4,51 +4,17 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { illegalArgument } from 'vs/base/common/errors';
import * as instantiation from './instantiation';
export class AbstractDescriptor<T> {
constructor(private _staticArguments: any[]) {
// empty
}
export class SyncDescriptor<T> {
public appendStaticArguments(more: any[]): void {
this._staticArguments.push.apply(this._staticArguments, more);
}
readonly ctor: any;
readonly staticArguments: any[];
public staticArguments(): any[];
public staticArguments(nth: number): any;
public staticArguments(nth?: number): any[] {
if (isNaN(nth)) {
return this._staticArguments.slice(0);
} else {
return this._staticArguments[nth];
}
}
_validate(type: T): void {
if (!type) {
throw illegalArgument('can not be falsy');
}
}
}
export class SyncDescriptor<T> extends AbstractDescriptor<T> {
constructor(private _ctor: any, ...staticArguments: any[]) {
super(staticArguments);
}
public get ctor(): any {
return this._ctor;
}
protected bind(...moreStaticArguments: any[]): SyncDescriptor<T> {
let allArgs: any[] = [];
allArgs = allArgs.concat(this.staticArguments());
allArgs = allArgs.concat(moreStaticArguments);
return new SyncDescriptor<T>(this._ctor, ...allArgs);
constructor(ctor: new (...args: any[]) => T, ..._staticArguments: any[]) {
this.ctor = ctor;
this.staticArguments = _staticArguments;
}
}
@@ -175,4 +141,4 @@ export interface SyncDescriptor8<A1, A2, A3, A4, A5, A6, A7, A8, T> {
bind(a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6): SyncDescriptor2<A7, A8, T>;
bind(a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7): SyncDescriptor1<A8, T>;
bind(a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8): SyncDescriptor0<T>;
}
}

View File

@@ -62,7 +62,7 @@ export class InstantiationService implements IInstantiationService {
}
}
createInstance<T>(param: any, ...rest: any[]): any {
createInstance(param: any, ...rest: any[]): any {
if (param instanceof SyncDescriptor) {
// sync
@@ -77,7 +77,7 @@ export class InstantiationService implements IInstantiationService {
private _createInstance<T>(desc: SyncDescriptor<T>, args: any[]): T {
// arguments given by createInstance-call and/or the descriptor
let staticArgs = desc.staticArguments().concat(args);
let staticArgs = desc.staticArguments.concat(args);
// arguments defined by service decorators
let serviceDependencies = _util.getServiceDependencies(desc.ctor).sort((a, b) => a.index - b.index);
@@ -117,9 +117,7 @@ export class InstantiationService implements IInstantiationService {
argArray.push(...staticArgs);
argArray.push(...serviceArgs);
const instance = create.apply(null, argArray);
desc._validate(instance);
return <T>instance;
return <T>create.apply(null, argArray);
}
private _getOrCreateServiceInstance<T>(id: ServiceIdentifier<T>): T {

View File

@@ -39,11 +39,11 @@ export class TestInstantiationService extends InstantiationService {
return <T>this._create(service, { mock: true });
}
public stub<T>(service?: ServiceIdentifier<T>, ctor?: any): T
public stub<T>(service?: ServiceIdentifier<T>, obj?: any): T
public stub<T>(service?: ServiceIdentifier<T>, ctor?: any, property?: string, value?: any): sinon.SinonStub
public stub<T>(service?: ServiceIdentifier<T>, obj?: any, property?: string, value?: any): sinon.SinonStub
public stub<T>(service?: ServiceIdentifier<T>, property?: string, value?: any): sinon.SinonStub
public stub<T>(service?: ServiceIdentifier<T>, ctor?: any): T;
public stub<T>(service?: ServiceIdentifier<T>, obj?: any): T;
public stub<T>(service?: ServiceIdentifier<T>, ctor?: any, property?: string, value?: any): sinon.SinonStub;
public stub<T>(service?: ServiceIdentifier<T>, obj?: any, property?: string, value?: any): sinon.SinonStub;
public stub<T>(service?: ServiceIdentifier<T>, property?: string, value?: any): sinon.SinonStub;
public stub<T>(serviceIdentifier?: ServiceIdentifier<T>, arg2?: any, arg3?: string, arg4?: any): sinon.SinonStub {
let service = typeof arg2 !== 'string' ? arg2 : void 0;
let serviceMock: IServiceMock<any> = { id: serviceIdentifier, service: service };
@@ -70,10 +70,10 @@ export class TestInstantiationService extends InstantiationService {
return stubObject;
}
public stubPromise<T>(service?: ServiceIdentifier<T>, fnProperty?: string, value?: any): T | sinon.SinonStub
public stubPromise<T>(service?: ServiceIdentifier<T>, ctor?: any, fnProperty?: string, value?: any): sinon.SinonStub
public stubPromise<T>(service?: ServiceIdentifier<T>, obj?: any, fnProperty?: string, value?: any): sinon.SinonStub
public stubPromise<T>(arg1?: any, arg2?: any, arg3?: any, arg4?: any): sinon.SinonStub {
public stubPromise<T>(service?: ServiceIdentifier<T>, fnProperty?: string, value?: any): T | sinon.SinonStub;
public stubPromise<T>(service?: ServiceIdentifier<T>, ctor?: any, fnProperty?: string, value?: any): sinon.SinonStub;
public stubPromise<T>(service?: ServiceIdentifier<T>, obj?: any, fnProperty?: string, value?: any): sinon.SinonStub;
public stubPromise(arg1?: any, arg2?: any, arg3?: any, arg4?: any): sinon.SinonStub {
arg3 = typeof arg2 === 'string' ? TPromise.as(arg3) : arg3;
arg4 = typeof arg2 !== 'string' && typeof arg3 === 'string' ? TPromise.as(arg4) : arg4;
return this.stub(arg1, arg2, arg3, arg4);
@@ -85,9 +85,9 @@ export class TestInstantiationService extends InstantiationService {
return spy;
}
private _create<T>(serviceMock: IServiceMock<T>, options: SinonOptions, reset?: boolean): any
private _create<T>(ctor: any, options: SinonOptions): any
private _create<T>(arg1: any, options: SinonOptions, reset: boolean = false): any {
private _create<T>(serviceMock: IServiceMock<T>, options: SinonOptions, reset?: boolean): any;
private _create<T>(ctor: any, options: SinonOptions): any;
private _create(arg1: any, options: SinonOptions, reset: boolean = false): any {
if (this.isServiceMock(arg1)) {
let service = this._getOrCreateService(arg1, options, reset);
this._serviceCollection.set(arg1.id, service);

View File

@@ -22,7 +22,7 @@ interface IStorageData {
}
class IntegrityStorage {
private static KEY = 'integrityService';
private static readonly KEY = 'integrityService';
private _storageService: IStorageService;
private _value: IStorageData;

View File

@@ -5,7 +5,7 @@
'use strict';
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { ContextKeyExpr, IContext } from 'vs/platform/contextkey/common/contextkey';
import { ContextKeyExpr, IContext, ContextKeyAndExpr } from 'vs/platform/contextkey/common/contextkey';
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
import { CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
@@ -121,7 +121,7 @@ export class KeybindingResolver {
continue;
}
if (KeybindingResolver.whenIsEntirelyIncluded(true, conflict.when, item.when)) {
if (KeybindingResolver.whenIsEntirelyIncluded(conflict.when, item.when)) {
// `item` completely overwrites `conflict`
// Remove conflict from the lookupMap
this._removeFromLookupMap(conflict);
@@ -160,15 +160,10 @@ export class KeybindingResolver {
}
/**
* Returns true if `a` is completely covered by `b`.
* Returns true if `b` is a more relaxed `a`.
* Return true if (`a` === true implies `b` === true).
* Returns true if it is provable `a` implies `b`.
* **Precondition**: Assumes `a` and `b` are normalized!
*/
public static whenIsEntirelyIncluded(inNormalizedForm: boolean, a: ContextKeyExpr, b: ContextKeyExpr): boolean {
if (!inNormalizedForm) {
a = a ? a.normalize() : null;
b = b ? b.normalize() : null;
}
public static whenIsEntirelyIncluded(a: ContextKeyExpr, b: ContextKeyExpr): boolean {
if (!b) {
return true;
}
@@ -176,16 +171,22 @@ export class KeybindingResolver {
return false;
}
let aRulesArr = a.serialize().split(' && ');
let bRulesArr = b.serialize().split(' && ');
const aExpressions: ContextKeyExpr[] = ((a instanceof ContextKeyAndExpr) ? a.expr : [a]);
const bExpressions: ContextKeyExpr[] = ((b instanceof ContextKeyAndExpr) ? b.expr : [b]);
let aRules: { [rule: string]: boolean; } = Object.create(null);
for (let i = 0, len = aRulesArr.length; i < len; i++) {
aRules[aRulesArr[i]] = true;
}
let aIndex = 0;
for (let bIndex = 0; bIndex < bExpressions.length; bIndex++) {
let bExpr = bExpressions[bIndex];
let bExprMatched = false;
while (!bExprMatched && aIndex < aExpressions.length) {
let aExpr = aExpressions[aIndex];
if (aExpr.equals(bExpr)) {
bExprMatched = true;
}
aIndex++;
}
for (let i = 0, len = bRulesArr.length; i < len; i++) {
if (!aRules[bRulesArr[i]]) {
if (!bExprMatched) {
return false;
}
}

View File

@@ -75,6 +75,7 @@ export interface IKeybindingsRegistry {
class KeybindingsRegistryImpl implements IKeybindingsRegistry {
private _keybindings: IKeybindingItem[];
private _keybindingsSorted: boolean;
public WEIGHT = {
editorCore: (importance: number = 0): number => {
@@ -96,6 +97,7 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry {
constructor() {
this._keybindings = [];
this._keybindingsSorted = true;
}
/**
@@ -144,11 +146,14 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry {
let actualKb = KeybindingsRegistryImpl.bindToCurrentPlatform(rule);
if (actualKb && actualKb.primary) {
this.registerDefaultKeybinding(createKeybinding(actualKb.primary, OS), rule.id, rule.weight, 0, rule.when);
this._registerDefaultKeybinding(createKeybinding(actualKb.primary, OS), rule.id, rule.weight, 0, rule.when);
}
if (actualKb && Array.isArray(actualKb.secondary)) {
actualKb.secondary.forEach((k, i) => this.registerDefaultKeybinding(createKeybinding(k, OS), rule.id, rule.weight, -i - 1, rule.when));
for (let i = 0, len = actualKb.secondary.length; i < len; i++) {
const k = actualKb.secondary[i];
this._registerDefaultKeybinding(createKeybinding(k, OS), rule.id, rule.weight, -i - 1, rule.when);
}
}
}
@@ -156,7 +161,7 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry {
let actualKb = KeybindingsRegistryImpl.bindToCurrentPlatform2(rule);
if (actualKb && actualKb.primary) {
this.registerDefaultKeybinding(actualKb.primary, rule.id, rule.weight, 0, rule.when);
this._registerDefaultKeybinding(actualKb.primary, rule.id, rule.weight, 0, rule.when);
}
}
@@ -199,7 +204,7 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry {
}
}
private registerDefaultKeybinding(keybinding: Keybinding, commandId: string, weight1: number, weight2: number, when: ContextKeyExpr): void {
private _registerDefaultKeybinding(keybinding: Keybinding, commandId: string, weight1: number, weight2: number, when: ContextKeyExpr): void {
if (OS === OperatingSystem.Windows) {
if (keybinding.type === KeybindingType.Chord) {
this._assertNoCtrlAlt(keybinding.firstPart, commandId);
@@ -215,12 +220,15 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry {
weight1: weight1,
weight2: weight2
});
this._keybindingsSorted = false;
}
public getDefaultKeybindings(): IKeybindingItem[] {
let result = this._keybindings.slice(0);
result.sort(sorter);
return result;
if (!this._keybindingsSorted) {
this._keybindings.sort(sorter);
this._keybindingsSorted = true;
}
return this._keybindings.slice(0);
}
}
export const KeybindingsRegistry: IKeybindingsRegistry = new KeybindingsRegistryImpl();

View File

@@ -124,8 +124,8 @@ suite('AbstractKeybindingService', () => {
let messageService: IMessageService = {
_serviceBrand: undefined,
hideAll: undefined,
confirmSync: undefined,
confirm: undefined,
confirmWithCheckbox: undefined,
show: (sev: Severity, message: any): () => void => {
showMessageCalls.push({
sev: sev,

View File

@@ -28,7 +28,7 @@ suite('KeybindingResolver', () => {
resolvedKeybinding,
command,
commandArgs,
when,
when ? when.normalize() : null,
isDefault
);
}
@@ -194,10 +194,14 @@ suite('KeybindingResolver', () => {
test('contextIsEntirelyIncluded', function () {
let assertIsIncluded = (a: ContextKeyExpr[], b: ContextKeyExpr[]) => {
assert.equal(KeybindingResolver.whenIsEntirelyIncluded(false, new ContextKeyAndExpr(a), new ContextKeyAndExpr(b)), true);
let tmpA = new ContextKeyAndExpr(a).normalize();
let tmpB = new ContextKeyAndExpr(b).normalize();
assert.equal(KeybindingResolver.whenIsEntirelyIncluded(tmpA, tmpB), true);
};
let assertIsNotIncluded = (a: ContextKeyExpr[], b: ContextKeyExpr[]) => {
assert.equal(KeybindingResolver.whenIsEntirelyIncluded(false, new ContextKeyAndExpr(a), new ContextKeyAndExpr(b)), false);
let tmpA = new ContextKeyAndExpr(a).normalize();
let tmpB = new ContextKeyAndExpr(b).normalize();
assert.equal(KeybindingResolver.whenIsEntirelyIncluded(tmpA, tmpB), false);
};
let key1IsTrue = ContextKeyExpr.equals('key1', true);
let key1IsNotFalse = ContextKeyExpr.notEquals('key1', false);

View File

@@ -7,19 +7,17 @@
import { ResolvedKeybinding, Keybinding, SimpleKeybinding } from 'vs/base/common/keyCodes';
import Event from 'vs/base/common/event';
import { IKeybindingService, IKeybindingEvent, IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding';
import { IContextKey, IContextKeyService, IContextKeyServiceTarget, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IContextKey, IContextKeyService, IContextKeyServiceTarget, ContextKeyExpr, IContextKeyChangeEvent } from 'vs/platform/contextkey/common/contextkey';
import { IResolveResult } from 'vs/platform/keybinding/common/keybindingResolver';
import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding';
import { OS } from 'vs/base/common/platform';
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
class MockKeybindingContextKey<T> implements IContextKey<T> {
private _key: string;
private _defaultValue: T;
private _value: T;
constructor(key: string, defaultValue: T) {
this._key = key;
constructor(defaultValue: T) {
this._defaultValue = defaultValue;
this._value = this._defaultValue;
}
@@ -46,14 +44,14 @@ export class MockContextKeyService implements IContextKeyService {
//
}
public createKey<T>(key: string, defaultValue: T): IContextKey<T> {
let ret = new MockKeybindingContextKey(key, defaultValue);
let ret = new MockKeybindingContextKey(defaultValue);
this._keys.set(key, ret);
return ret;
}
public contextMatchesRules(rules: ContextKeyExpr): boolean {
return false;
}
public get onDidChangeContext(): Event<string[]> {
public get onDidChangeContext(): Event<IContextKeyChangeEvent> {
return Event.None;
}
public getContextKeyValue(key: string) {

View File

@@ -47,8 +47,9 @@ export enum StartupKind {
export enum LifecyclePhase {
Starting = 1,
Running = 2,
ShuttingDown = 3
Restoring = 2,
Running = 3,
Eventually = 4
}
/**
@@ -70,9 +71,10 @@ export interface ILifecycleService {
readonly phase: LifecyclePhase;
/**
* An event that fire when the lifecycle phase has changed
* Returns a promise that resolves when a certain lifecycle phase
* has started.
*/
readonly onDidChangePhase: Event<LifecyclePhase>;
when(phase: LifecyclePhase): Thenable<void>;
/**
* Fired before shutdown happens. Allows listeners to veto against the
@@ -92,8 +94,8 @@ export interface ILifecycleService {
export const NullLifecycleService: ILifecycleService = {
_serviceBrand: null,
phase: LifecyclePhase.Running,
when() { return Promise.resolve(); },
startupKind: StartupKind.NewWindow,
onDidChangePhase: Event.None,
onWillShutdown: Event.None,
onShutdown: Event.None
};
@@ -127,4 +129,4 @@ export function handleVetos(vetos: (boolean | TPromise<boolean>)[], onError: (er
}
return TPromise.join(promises).then(() => lazyValue);
}
}

View File

@@ -7,9 +7,8 @@
import { ipcMain as ipc, app } from 'electron';
import { TPromise, TValueCallback } from 'vs/base/common/winjs.base';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ILogService } from 'vs/platform/log/common/log';
import { IStorageService } from 'vs/platform/storage/node/storage';
import { IStateService } from 'vs/platform/state/common/state';
import Event, { Emitter } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ICodeWindow } from 'vs/platform/windows/electron-main/windows';
@@ -75,7 +74,7 @@ export class LifecycleService implements ILifecycleService {
_serviceBrand: any;
private static QUIT_FROM_RESTART_MARKER = 'quit.from.restart'; // use a marker to find out if the session was restarted
private static readonly QUIT_FROM_RESTART_MARKER = 'quit.from.restart'; // use a marker to find out if the session was restarted
private windowToCloseRequest: { [windowId: string]: boolean };
private quitRequested: boolean;
@@ -94,9 +93,8 @@ export class LifecycleService implements ILifecycleService {
onBeforeWindowUnload: Event<IWindowUnloadEvent> = this._onBeforeWindowUnload.event;
constructor(
@IEnvironmentService private environmentService: IEnvironmentService,
@ILogService private logService: ILogService,
@IStorageService private storageService: IStorageService
@IStateService private stateService: IStateService
) {
this.windowToCloseRequest = Object.create(null);
this.quitRequested = false;
@@ -107,10 +105,10 @@ export class LifecycleService implements ILifecycleService {
}
private handleRestarted(): void {
this._wasRestarted = !!this.storageService.getItem(LifecycleService.QUIT_FROM_RESTART_MARKER);
this._wasRestarted = !!this.stateService.getItem(LifecycleService.QUIT_FROM_RESTART_MARKER);
if (this._wasRestarted) {
this.storageService.removeItem(LifecycleService.QUIT_FROM_RESTART_MARKER); // remove the marker right after if found
this.stateService.removeItem(LifecycleService.QUIT_FROM_RESTART_MARKER); // remove the marker right after if found
}
}
@@ -126,7 +124,7 @@ export class LifecycleService implements ILifecycleService {
// before-quit
app.on('before-quit', (e) => {
this.logService.log('Lifecycle#before-quit');
this.logService.trace('Lifecycle#before-quit');
if (!this.quitRequested) {
this._onBeforeQuit.fire(); // only send this if this is the first quit request we have
@@ -137,7 +135,7 @@ export class LifecycleService implements ILifecycleService {
// window-all-closed
app.on('window-all-closed', () => {
this.logService.log('Lifecycle#window-all-closed');
this.logService.trace('Lifecycle#window-all-closed');
// Windows/Linux: we quit when all windows have closed
// Mac: we only quit when quit was requested
@@ -152,11 +150,11 @@ export class LifecycleService implements ILifecycleService {
// Window Before Closing: Main -> Renderer
window.win.on('close', e => {
const windowId = window.id;
this.logService.log('Lifecycle#window-before-close', windowId);
this.logService.trace('Lifecycle#window-before-close', windowId);
// The window already acknowledged to be closed
if (this.windowToCloseRequest[windowId]) {
this.logService.log('Lifecycle#window-close', windowId);
this.logService.trace('Lifecycle#window-close', windowId);
delete this.windowToCloseRequest[windowId];
@@ -185,7 +183,7 @@ export class LifecycleService implements ILifecycleService {
return TPromise.as<boolean>(false);
}
this.logService.log('Lifecycle#unload()', window.id);
this.logService.trace('Lifecycle#unload()', window.id);
const windowUnloadReason = this.quitRequested ? UnloadReason.QUIT : reason;
@@ -249,7 +247,7 @@ export class LifecycleService implements ILifecycleService {
* by the user or not.
*/
public quit(fromUpdate?: boolean): TPromise<boolean /* veto */> {
this.logService.log('Lifecycle#quit()');
this.logService.trace('Lifecycle#quit()');
if (!this.pendingQuitPromise) {
this.pendingQuitPromise = new TPromise<boolean>(c => {
@@ -260,7 +258,7 @@ export class LifecycleService implements ILifecycleService {
app.once('will-quit', () => {
if (this.pendingQuitPromiseComplete) {
if (fromUpdate) {
this.storageService.setItem(LifecycleService.QUIT_FROM_RESTART_MARKER, true);
this.stateService.setItem(LifecycleService.QUIT_FROM_RESTART_MARKER, true);
}
this.pendingQuitPromiseComplete(false /* no veto */);
@@ -298,7 +296,7 @@ export class LifecycleService implements ILifecycleService {
let vetoed = false;
app.once('quit', () => {
if (!vetoed) {
this.storageService.setItem(LifecycleService.QUIT_FROM_RESTART_MARKER, true);
this.stateService.setItem(LifecycleService.QUIT_FROM_RESTART_MARKER, true);
app.relaunch({ args });
}
});

View File

@@ -4,12 +4,20 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { List } from 'vs/base/browser/ui/list/listWidget';
import { ITree, ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree';
import { List, IListOptions } from 'vs/base/browser/ui/list/listWidget';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { RunOnceScheduler } from 'vs/base/common/async';
import { IDisposable, toDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle';
import { IContextKeyService, IContextKey, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { PagedList, IPagedRenderer } from 'vs/base/browser/ui/list/listPaging';
import { IDelegate, IRenderer } from 'vs/base/browser/ui/list/list';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { attachListStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import Event, { Emitter } from 'vs/base/common/event';
import { InputFocusedContextKey } from 'vs/platform/workbench/common/contextkeys';
export type ListWidget = List<any> | PagedList<any> | ITree;
export const IListService = createDecorator<IListService>('listService');
@@ -17,52 +25,32 @@ export interface IListService {
_serviceBrand: any;
/**
* Makes a tree or list widget known to the list service. It will track the lists focus and
* blur events to update context keys based on the widget being focused or not.
*
* @param extraContextKeys an optional list of additional context keys to update based on
* the widget being focused or not.
*/
register(tree: ITree, extraContextKeys?: (IContextKey<boolean>)[]): IDisposable;
register(list: List<any>, extraContextKeys?: (IContextKey<boolean>)[]): IDisposable;
/**
* Returns the currently focused list widget if any.
*/
getFocused(): ITree | List<any>;
readonly lastFocusedList: ListWidget | undefined;
}
export const ListFocusContext = new RawContextKey<boolean>('listFocus', false);
interface IRegisteredList {
widget: ITree | List<any>;
widget: ListWidget;
extraContextKeys?: (IContextKey<boolean>)[];
}
export class ListService implements IListService {
public _serviceBrand: any;
_serviceBrand: any;
private focusedTreeOrList: ITree | List<any>;
private lists: IRegisteredList[];
private lists: IRegisteredList[] = [];
private _lastFocusedWidget: ListWidget | undefined = undefined;
private listFocusContext: IContextKey<boolean>;
private focusChangeScheduler: RunOnceScheduler;
constructor(
@IContextKeyService contextKeyService: IContextKeyService
) {
this.listFocusContext = ListFocusContext.bindTo(contextKeyService);
this.lists = [];
this.focusChangeScheduler = new RunOnceScheduler(() => this.onFocusChange(), 50 /* delay until the focus/blur dust settles */);
get lastFocusedList(): ListWidget | undefined {
return this._lastFocusedWidget;
}
public register(tree: ITree, extraContextKeys?: (IContextKey<boolean>)[]): IDisposable;
public register(list: List<any>, extraContextKeys?: (IContextKey<boolean>)[]): IDisposable;
public register(widget: ITree | List<any>, extraContextKeys?: (IContextKey<boolean>)[]): IDisposable {
if (this.indexOf(widget) >= 0) {
constructor( @IContextKeyService contextKeyService: IContextKeyService) { }
register(widget: ListWidget, extraContextKeys?: (IContextKey<boolean>)[]): IDisposable {
if (this.lists.some(l => l.widget === widget)) {
throw new Error('Cannot register the same widget multiple times');
}
@@ -72,83 +60,115 @@ export class ListService implements IListService {
// Check for currently being focused
if (widget.isDOMFocused()) {
this.setFocusedList(registeredList);
this._lastFocusedWidget = widget;
}
const toDispose = [
widget.onDOMFocus(() => this.focusChangeScheduler.schedule()),
widget.onDOMBlur(() => this.focusChangeScheduler.schedule())
];
const result = combinedDisposable([
widget.onDidFocus(() => this._lastFocusedWidget = widget),
toDisposable(() => this.lists.splice(this.lists.indexOf(registeredList), 1))
]);
// Special treatment for tree highlight mode
if (!(widget instanceof List)) {
const tree = widget;
return result;
}
}
toDispose.push(tree.onHighlightChange(() => {
this.focusChangeScheduler.schedule();
}));
}
const RawWorkbenchListFocusContextKey = new RawContextKey<boolean>('listFocus', true);
export const WorkbenchListFocusContextKey = ContextKeyExpr.and(RawWorkbenchListFocusContextKey, ContextKeyExpr.not(InputFocusedContextKey));
// Remove list once disposed
toDispose.push({
dispose: () => { this.lists.splice(this.lists.indexOf(registeredList), 1); }
});
export type Widget = List<any> | PagedList<any> | ITree;
return {
dispose: () => dispose(toDispose)
};
function createScopedContextKeyService(contextKeyService: IContextKeyService, widget: Widget): IContextKeyService {
const result = contextKeyService.createScoped(widget.getHTMLElement());
RawWorkbenchListFocusContextKey.bindTo(result);
return result;
}
export class WorkbenchList<T> extends List<T> {
readonly contextKeyService: IContextKeyService;
private disposable: IDisposable;
constructor(
container: HTMLElement,
delegate: IDelegate<T>,
renderers: IRenderer<T, any>[],
options: IListOptions<T>,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService
) {
super(container, delegate, renderers, options);
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
this.disposable = combinedDisposable([
this.contextKeyService,
(listService as ListService).register(this),
attachListStyler(this, themeService)
]);
}
private indexOf(widget: ITree | List<any>): number {
for (let i = 0; i < this.lists.length; i++) {
const list = this.lists[i];
if (list.widget === widget) {
return i;
}
}
dispose(): void {
this.disposable.dispose();
}
}
return -1;
export class WorkbenchPagedList<T> extends PagedList<T> {
readonly contextKeyService: IContextKeyService;
private disposable: IDisposable;
constructor(
container: HTMLElement,
delegate: IDelegate<number>,
renderers: IPagedRenderer<T, any>[],
options: IListOptions<any>,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService
) {
super(container, delegate, renderers, options);
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
this.disposable = combinedDisposable([
this.contextKeyService,
(listService as ListService).register(this),
attachListStyler(this, themeService)
]);
}
private onFocusChange(): void {
let focusedList: IRegisteredList;
for (let i = 0; i < this.lists.length; i++) {
const list = this.lists[i];
if (document.activeElement === list.widget.getHTMLElement()) {
focusedList = list;
break;
}
}
dispose(): void {
this.disposable.dispose();
}
}
this.setFocusedList(focusedList);
export class WorkbenchTree extends Tree {
private _onFocusChange = new Emitter<boolean>();
readonly onFocusChange: Event<boolean> = this._onFocusChange.event;
readonly contextKeyService: IContextKeyService;
private disposables: IDisposable[] = [];
constructor(
container: HTMLElement,
configuration: ITreeConfiguration,
options: ITreeOptions,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService
) {
super(container, configuration, options);
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
this.disposables.push(
this.contextKeyService,
(listService as ListService).register(this),
attachListStyler(this, themeService)
);
}
private setFocusedList(focusedList?: IRegisteredList): void {
// First update our context
if (focusedList) {
this.focusedTreeOrList = focusedList.widget;
this.listFocusContext.set(true);
} else {
this.focusedTreeOrList = void 0;
this.listFocusContext.set(false);
}
// Then check for extra contexts to unset
for (let i = 0; i < this.lists.length; i++) {
const list = this.lists[i];
if (list !== focusedList && list.extraContextKeys) {
list.extraContextKeys.forEach(key => key.set(false));
}
}
// Finally set context for focused list if there are any
if (focusedList && focusedList.extraContextKeys) {
focusedList.extraContextKeys.forEach(key => key.set(true));
}
dispose(): void {
this.disposables = dispose(this.disposables);
}
public getFocused(): ITree | List<any> {
return this.focusedTreeOrList;
}
}
}

View File

@@ -5,37 +5,246 @@
'use strict';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { createDecorator as createServiceDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable } from 'vs/base/common/lifecycle';
import { isWindows } from 'vs/base/common/platform';
export const ILogService = createDecorator<ILogService>('logService');
export const ILogService = createServiceDecorator<ILogService>('logService');
export interface ILogService {
_serviceBrand: any;
log(...args: any[]): void;
warn(...args: any[]): void;
error(...args: any[]): void;
export enum LogLevel {
Trace,
Debug,
Info,
Warning,
Error,
Critical,
Off
}
export class LogMainService implements ILogService {
export interface ILogService extends IDisposable {
_serviceBrand: any;
constructor( @IEnvironmentService private environmentService: IEnvironmentService) {
setLevel(level: LogLevel): void;
getLevel(): LogLevel;
trace(message: string, ...args: any[]): void;
debug(message: string, ...args: any[]): void;
info(message: string, ...args: any[]): void;
warn(message: string, ...args: any[]): void;
error(message: string | Error, ...args: any[]): void;
critical(message: string | Error, ...args: any[]): void;
}
export class ConsoleLogMainService implements ILogService {
_serviceBrand: any;
private level: LogLevel = LogLevel.Error;
private useColors: boolean;
constructor( @IEnvironmentService environmentService: IEnvironmentService) {
this.setLevel(environmentService.logLevel);
this.useColors = !isWindows;
}
public log(...args: any[]): void {
if (this.environmentService.verbose) {
console.log(`\x1b[90m[main ${new Date().toLocaleTimeString()}]\x1b[0m`, ...args);
setLevel(level: LogLevel): void {
this.level = level;
}
getLevel(): LogLevel {
return this.level;
}
trace(message: string, ...args: any[]): void {
if (this.level <= LogLevel.Trace) {
if (this.useColors) {
console.log(`\x1b[90m[main ${new Date().toLocaleTimeString()}]\x1b[0m`, message, ...args);
} else {
console.log(`[main ${new Date().toLocaleTimeString()}]`, message, ...args);
}
}
}
public error(...args: any[]): void {
console.error(`\x1b[91m[main ${new Date().toLocaleTimeString()}]\x1b[0m`, ...args);
debug(message: string, ...args: any[]): void {
if (this.level <= LogLevel.Debug) {
if (this.useColors) {
console.log(`\x1b[90m[main ${new Date().toLocaleTimeString()}]\x1b[0m`, message, ...args);
} else {
console.log(`[main ${new Date().toLocaleTimeString()}]`, message, ...args);
}
}
}
public warn(...args: any[]): void {
console.warn(`\x1b[93m[main ${new Date().toLocaleTimeString()}]\x1b[0m`, ...args);
info(message: string, ...args: any[]): void {
if (this.level <= LogLevel.Info) {
if (this.useColors) {
console.log(`\x1b[90m[main ${new Date().toLocaleTimeString()}]\x1b[0m`, message, ...args);
} else {
console.log(`[main ${new Date().toLocaleTimeString()}]`, message, ...args);
}
}
}
}
warn(message: string | Error, ...args: any[]): void {
if (this.level <= LogLevel.Warning) {
if (this.useColors) {
console.warn(`\x1b[93m[main ${new Date().toLocaleTimeString()}]\x1b[0m`, message, ...args);
} else {
console.warn(`[main ${new Date().toLocaleTimeString()}]`, message, ...args);
}
}
}
error(message: string, ...args: any[]): void {
if (this.level <= LogLevel.Error) {
if (this.useColors) {
console.error(`\x1b[91m[main ${new Date().toLocaleTimeString()}]\x1b[0m`, message, ...args);
} else {
console.error(`[main ${new Date().toLocaleTimeString()}]`, message, ...args);
}
}
}
critical(message: string, ...args: any[]): void {
if (this.level <= LogLevel.Critical) {
if (this.useColors) {
console.error(`\x1b[90m[main ${new Date().toLocaleTimeString()}]\x1b[0m`, message, ...args);
} else {
console.error(`[main ${new Date().toLocaleTimeString()}]`, message, ...args);
}
}
}
dispose(): void {
// noop
}
}
export class ConsoleLogService implements ILogService {
_serviceBrand: any;
private level: LogLevel = LogLevel.Error;
constructor( @IEnvironmentService environmentService: IEnvironmentService) {
this.setLevel(environmentService.logLevel);
}
setLevel(level: LogLevel): void {
this.level = level;
}
getLevel(): LogLevel {
return this.level;
}
trace(message: string, ...args: any[]): void {
if (this.level <= LogLevel.Trace) {
console.log('%cTRACE', 'color: #888', message, ...args);
}
}
debug(message: string, ...args: any[]): void {
if (this.level <= LogLevel.Debug) {
console.log('%cDEBUG', 'background: #eee; color: #888', message, ...args);
}
}
info(message: string, ...args: any[]): void {
if (this.level <= LogLevel.Info) {
console.log('%c INFO', 'color: #33f', message, ...args);
}
}
warn(message: string | Error, ...args: any[]): void {
if (this.level <= LogLevel.Warning) {
console.log('%c WARN', 'color: #993', message, ...args);
}
}
error(message: string, ...args: any[]): void {
if (this.level <= LogLevel.Error) {
console.log('%c ERR', 'color: #f33', message, ...args);
}
}
critical(message: string, ...args: any[]): void {
if (this.level <= LogLevel.Critical) {
console.log('%cCRITI', 'background: #f33; color: white', message, ...args);
}
}
dispose(): void { }
}
export class MultiplexLogService implements ILogService {
_serviceBrand: any;
constructor(private logServices: ILogService[]) { }
setLevel(level: LogLevel): void {
for (const logService of this.logServices) {
logService.setLevel(level);
}
}
getLevel(): LogLevel {
for (const logService of this.logServices) {
return logService.getLevel();
}
return LogLevel.Info;
}
trace(message: string, ...args: any[]): void {
for (const logService of this.logServices) {
logService.trace(message, ...args);
}
}
debug(message: string, ...args: any[]): void {
for (const logService of this.logServices) {
logService.debug(message, ...args);
}
}
info(message: string, ...args: any[]): void {
for (const logService of this.logServices) {
logService.info(message, ...args);
}
}
warn(message: string, ...args: any[]): void {
for (const logService of this.logServices) {
logService.warn(message, ...args);
}
}
error(message: string | Error, ...args: any[]): void {
for (const logService of this.logServices) {
logService.error(message, ...args);
}
}
critical(message: string | Error, ...args: any[]): void {
for (const logService of this.logServices) {
logService.critical(message, ...args);
}
}
dispose(): void {
for (const logService of this.logServices) {
logService.dispose();
}
}
}
export class NoopLogService implements ILogService {
_serviceBrand: any;
setLevel(level: LogLevel): void { }
getLevel(): LogLevel { return LogLevel.Info; }
trace(message: string, ...args: any[]): void { }
debug(message: string, ...args: any[]): void { }
info(message: string, ...args: any[]): void { }
warn(message: string, ...args: any[]): void { }
error(message: string | Error, ...args: any[]): void { }
critical(message: string | Error, ...args: any[]): void { }
dispose(): void { }
}

View File

@@ -0,0 +1,111 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as path from 'path';
import { ILogService, LogLevel, NoopLogService } from 'vs/platform/log/common/log';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { RotatingLogger, setAsyncMode } from 'spdlog';
export function createLogService(processName: string, environmentService: IEnvironmentService): ILogService {
try {
setAsyncMode(8192, 2000);
const logfilePath = path.join(environmentService.logsPath, `${processName}.log`);
const logger = new RotatingLogger(processName, logfilePath, 1024 * 1024 * 5, 6);
logger.setLevel(0);
return new SpdLogService(logger, environmentService.logLevel);
} catch (e) {
console.error(e);
}
return new NoopLogService();
}
class SpdLogService implements ILogService {
_serviceBrand: any;
constructor(
private readonly logger: RotatingLogger,
private level: LogLevel = LogLevel.Error
) {
}
setLevel(logLevel: LogLevel): void {
this.level = logLevel;
}
getLevel(): LogLevel {
return this.level;
}
trace(): void {
if (this.level <= LogLevel.Trace) {
this.logger.trace(this.format(arguments));
}
}
debug(): void {
if (this.level <= LogLevel.Debug) {
this.logger.debug(this.format(arguments));
}
}
info(): void {
if (this.level <= LogLevel.Info) {
this.logger.info(this.format(arguments));
}
}
warn(): void {
if (this.level <= LogLevel.Warning) {
this.logger.warn(this.format(arguments));
}
}
error(): void {
if (this.level <= LogLevel.Error) {
const arg = arguments[0];
if (arg instanceof Error) {
const array = Array.prototype.slice.call(arguments) as any[];
array[0] = arg.stack;
this.logger.error(this.format(array));
} else {
this.logger.error(this.format(arguments));
}
}
}
critical(): void {
if (this.level <= LogLevel.Critical) {
this.logger.critical(this.format(arguments));
}
}
dispose(): void {
this.logger.flush();
this.logger.drop();
}
private format(args: any): string {
let result = '';
for (let i = 0; i < args.length; i++) {
let a = args[i];
if (typeof a === 'object') {
try {
a = JSON.stringify(a);
} catch (e) { }
}
result += (i > 0 ? ' ' : '') + a;
}
return result;
}
}

View File

@@ -379,7 +379,7 @@ class MultiLineMatcher extends AbstractLineMatcher {
} else {
// Only the last pattern can loop
if (pattern.loop && i === this.patterns.length - 1) {
data = Objects.clone(data);
data = Objects.deepClone(data);
}
this.fillProblemData(data, pattern, matches);
}
@@ -399,21 +399,13 @@ class MultiLineMatcher extends AbstractLineMatcher {
this.data = null;
return null;
}
let data = Objects.clone(this.data);
let data = Objects.deepClone(this.data);
this.fillProblemData(data, pattern, matches);
return this.getMarkerMatch(data);
}
}
export namespace Config {
/**
* Defines possible problem severity values
*/
export namespace ProblemSeverity {
export const Error: string = 'error';
export const Warning: string = 'warning';
export const Info: string = 'info';
}
export interface ProblemPattern {
@@ -911,8 +903,8 @@ export namespace Schemas {
}
};
export const NamedProblemPattern: IJSONSchema = Objects.clone(ProblemPattern);
NamedProblemPattern.properties = Objects.clone(NamedProblemPattern.properties);
export const NamedProblemPattern: IJSONSchema = Objects.deepClone(ProblemPattern);
NamedProblemPattern.properties = Objects.deepClone(NamedProblemPattern.properties);
NamedProblemPattern.properties['name'] = {
type: 'string',
description: localize('NamedProblemPatternSchema.name', 'The name of the problem pattern.')
@@ -955,7 +947,6 @@ let problemPatternExtPoint = ExtensionsRegistry.registerExtensionPoint<Config.Na
export interface IProblemPatternRegistry {
onReady(): TPromise<void>;
exists(key: string): boolean;
get(key: string): ProblemPattern | MultiLineProblemPattern;
}
@@ -1016,14 +1007,6 @@ class ProblemPatternRegistryImpl implements IProblemPatternRegistry {
return this.patterns[key];
}
public exists(key: string): boolean {
return !!this.patterns[key];
}
public remove(key: string): void {
delete this.patterns[key];
}
private fillDefaults(): void {
this.add('msCompile', {
regexp: /^(?:\s+\d+\>)?([^\s].*)\((\d+|\d+,\d+|\d+,\d+,\d+,\d+)\)\s*:\s+(error|warning|info)\s+(\w{1,2}\d+)\s*:\s*(.*)$/,
@@ -1215,7 +1198,7 @@ export class ProblemMatcherParser extends Parser {
if (variableName.length > 1 && variableName[0] === '$') {
let base = ProblemMatcherRegistry.get(variableName.substring(1));
if (base) {
result = Objects.clone(base);
result = Objects.deepClone(base);
if (description.owner) {
result.owner = owner;
}
@@ -1475,8 +1458,8 @@ export namespace Schemas {
}
};
export const LegacyProblemMatcher: IJSONSchema = Objects.clone(ProblemMatcher);
LegacyProblemMatcher.properties = Objects.clone(LegacyProblemMatcher.properties);
export const LegacyProblemMatcher: IJSONSchema = Objects.deepClone(ProblemMatcher);
LegacyProblemMatcher.properties = Objects.deepClone(LegacyProblemMatcher.properties);
LegacyProblemMatcher.properties['watchedTaskBeginsRegExp'] = {
type: 'string',
deprecationMessage: localize('LegacyProblemMatcherSchema.watchedBegin.deprecated', 'This property is deprecated. Use the watching property instead.'),
@@ -1488,8 +1471,8 @@ export namespace Schemas {
description: localize('LegacyProblemMatcherSchema.watchedEnd', 'A regular expression signaling that a watched tasks ends executing.')
};
export const NamedProblemMatcher: IJSONSchema = Objects.clone(ProblemMatcher);
NamedProblemMatcher.properties = Objects.clone(NamedProblemMatcher.properties);
export const NamedProblemMatcher: IJSONSchema = Objects.deepClone(ProblemMatcher);
NamedProblemMatcher.properties = Objects.deepClone(NamedProblemMatcher.properties);
NamedProblemMatcher.properties.name = {
type: 'string',
description: localize('NamedProblemMatcherSchema.name', 'The name of the problem matcher used to refer to it.')
@@ -1508,9 +1491,7 @@ let problemMatchersExtPoint = ExtensionsRegistry.registerExtensionPoint<Config.N
export interface IProblemMatcherRegistry {
onReady(): TPromise<void>;
exists(name: string): boolean;
get(name: string): NamedProblemMatcher;
values(): NamedProblemMatcher[];
keys(): string[];
}
@@ -1547,6 +1528,7 @@ class ProblemMatcherRegistryImpl implements IProblemMatcherRegistry {
}
public onReady(): TPromise<void> {
ProblemPatternRegistry.onReady();
return this.readyPromise;
}
@@ -1558,22 +1540,10 @@ class ProblemMatcherRegistryImpl implements IProblemMatcherRegistry {
return this.matchers[name];
}
public exists(name: string): boolean {
return !!this.matchers[name];
}
public remove(name: string): void {
delete this.matchers[name];
}
public keys(): string[] {
return Object.keys(this.matchers);
}
public values(): NamedProblemMatcher[] {
return Object.keys(this.matchers).map(key => this.matchers[key]);
}
private fillDefaults(): void {
this.add({
name: 'msCompile',

View File

@@ -62,12 +62,12 @@ export interface IMessageService {
/**
* Ask the user for confirmation.
*/
confirmSync(confirmation: IConfirmation): boolean;
confirm(confirmation: IConfirmation): boolean;
/**
* Ask the user for confirmation without blocking.
* Ask the user for confirmation with a checkbox.
*/
confirm(confirmation: IConfirmation): TPromise<IConfirmationResult>;
confirmWithCheckbox(confirmation: IConfirmation): TPromise<IConfirmationResult>;
}
export const IChoiceService = createDecorator<IChoiceService>('choiceService');

View File

@@ -20,16 +20,19 @@ export interface IProductConfiguration {
quality?: string;
commit?: string;
settingsSearchBuildId?: number;
settingsSearchUrl?: string;
date: string;
extensionsGallery: {
serviceUrl: string;
itemUrl: string;
controlUrl: string;
};
extensionTips: { [id: string]: string; };
extensionImportantTips: { [id: string]: { name: string; pattern: string; }; };
exeBasedExtensionTips: { [id: string]: any; };
extensionKeywords: { [extension: string]: string[]; };
extensionAllowedBadgeProviders: string[];
extensionAllowedProposedApi: string[];
keymapExtensionTips: string[];
crashReporter: {
companyName: string;

View File

@@ -39,7 +39,7 @@ export class OpenerService implements IOpenerService {
this._telemetryService.publicLog('openerService', { scheme: resource.scheme });
const { scheme, path, query, fragment } = resource;
let promise: TPromise<any>;
let promise: TPromise<any> = TPromise.wrap(void 0);
if (scheme === Schemas.http || scheme === Schemas.https || scheme === Schemas.mailto) {
// open http or default mail application
@@ -84,6 +84,6 @@ export class OpenerService implements IOpenerService {
promise = this._editorService.openEditor({ resource, options: { selection, } }, options && options.openToSide);
}
return TPromise.as(promise);
return promise;
}
}

View File

@@ -6,7 +6,6 @@
import Types = require('vs/base/common/types');
import Assert = require('vs/base/common/assert');
import { IInstantiationService, IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation';
export interface IRegistry {
@@ -58,42 +57,3 @@ class RegistryImpl implements IRegistry {
}
export const Registry = <IRegistry>new RegistryImpl();
/**
* A base class for registries that leverage the instantiation service to create instances.
*/
export class BaseRegistry<T> {
private toBeInstantiated: IConstructorSignature0<T>[] = [];
private instances: T[] = [];
private instantiationService: IInstantiationService;
public setInstantiationService(service: IInstantiationService): void {
this.instantiationService = service;
while (this.toBeInstantiated.length > 0) {
let entry = this.toBeInstantiated.shift();
this.instantiate(entry);
}
}
private instantiate(ctor: IConstructorSignature0<T>): void {
let instance = this.instantiationService.createInstance(ctor);
this.instances.push(instance);
}
_register(ctor: IConstructorSignature0<T>): void {
if (this.instantiationService) {
this.instantiate(ctor);
} else {
this.toBeInstantiated.push(ctor);
}
}
_getInstances(): T[] {
return this.instances.slice(0);
}
_setInstances(instances: T[]): void {
this.instances = instances;
}
}

View File

@@ -61,6 +61,11 @@ export const xhrRequest: IRequestFunction = (options: IRequestOptions): TPromise
stream: new ArrayBufferStream(xhr.response)
});
};
xhr.ontimeout = e => reject(new Error(`XHR timeout: ${options.timeout}ms`));
if (options.timeout) {
xhr.timeout = options.timeout;
}
xhr.send(options.data);
return null;

View File

@@ -28,8 +28,8 @@ export class RequestService implements IRequestService {
constructor(
@IConfigurationService configurationService: IConfigurationService
) {
this.configure(configurationService.getConfiguration<IHTTPConfiguration>());
configurationService.onDidChangeConfiguration(() => this.configure(configurationService.getConfiguration()), this, this.disposables);
this.configure(configurationService.getValue<IHTTPConfiguration>());
configurationService.onDidChangeConfiguration(() => this.configure(configurationService.getValue()), this, this.disposables);
}
private configure(config: IHTTPConfiguration) {

View File

@@ -174,10 +174,6 @@ export interface ISearchConfiguration extends IFilesConfiguration {
search: {
exclude: glob.IExpression;
useRipgrep: boolean;
/**
* Use ignore file for text search.
*/
useIgnoreFilesByDefault: boolean;
/**
* Use ignore file for file search.
*/
@@ -202,8 +198,9 @@ export function getExcludes(configuration: ISearchConfiguration): glob.IExpressi
}
let allExcludes: glob.IExpression = Object.create(null);
allExcludes = objects.mixin(allExcludes, fileExcludes);
allExcludes = objects.mixin(allExcludes, searchExcludes, true);
// clone the config as it could be frozen
allExcludes = objects.mixin(allExcludes, objects.deepClone(fileExcludes));
allExcludes = objects.mixin(allExcludes, objects.deepClone(searchExcludes), true);
return allExcludes;
}

View File

@@ -2,21 +2,17 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const ICredentialsService = createDecorator<ICredentialsService>('credentialsService');
export interface ICredentialsService {
export const IStateService = createDecorator<IStateService>('stateService');
export interface IStateService {
_serviceBrand: any;
readSecret(service: string, account: string): TPromise<string | undefined>;
writeSecret(service: string, account: string, secret: string): TPromise<void>;
deleteSecret(service: string, account: string): TPromise<boolean>;
getItem<T>(key: string, defaultValue?: T): T;
setItem(key: string, data: any): void;
removeItem(key: string): void;
}

View File

@@ -0,0 +1,110 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as path from 'path';
import * as fs from 'original-fs';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { writeFileAndFlushSync } from 'vs/base/node/extfs';
import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types';
import { IStateService } from 'vs/platform/state/common/state';
import { ILogService } from 'vs/platform/log/common/log';
export class FileStorage {
private database: object = null;
constructor(private dbPath: string, private onError: (error) => void) { }
private ensureLoaded(): void {
if (!this.database) {
this.database = this.loadSync();
}
}
public getItem<T>(key: string, defaultValue?: T): T {
this.ensureLoaded();
const res = this.database[key];
if (isUndefinedOrNull(res)) {
return defaultValue;
}
return res;
}
public setItem(key: string, data: any): void {
this.ensureLoaded();
// Remove an item when it is undefined or null
if (isUndefinedOrNull(data)) {
return this.removeItem(key);
}
// Shortcut for primitives that did not change
if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') {
if (this.database[key] === data) {
return;
}
}
this.database[key] = data;
this.saveSync();
}
public removeItem(key: string): void {
this.ensureLoaded();
// Only update if the key is actually present (not undefined)
if (!isUndefined(this.database[key])) {
this.database[key] = void 0;
this.saveSync();
}
}
private loadSync(): object {
try {
return JSON.parse(fs.readFileSync(this.dbPath).toString()); // invalid JSON or permission issue can happen here
} catch (error) {
if (error && error.code !== 'ENOENT') {
this.onError(error);
}
return {};
}
}
private saveSync(): void {
try {
writeFileAndFlushSync(this.dbPath, JSON.stringify(this.database, null, 4)); // permission issue can happen here
} catch (error) {
this.onError(error);
}
}
}
export class StateService implements IStateService {
_serviceBrand: any;
private fileStorage: FileStorage;
constructor( @IEnvironmentService environmentService: IEnvironmentService, @ILogService logService: ILogService) {
this.fileStorage = new FileStorage(path.join(environmentService.userDataPath, 'storage.json'), error => logService.error(error));
}
public getItem<T>(key: string, defaultValue?: T): T {
return this.fileStorage.getItem(key, defaultValue);
}
public setItem(key: string, data: any): void {
this.fileStorage.setItem(key, data);
}
public removeItem(key: string): void {
this.fileStorage.removeItem(key);
}
}

View File

@@ -0,0 +1,56 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import os = require('os');
import path = require('path');
import extfs = require('vs/base/node/extfs');
import { getRandomTestPath } from 'vs/workbench/test/workbenchTestServices';
import { writeFileAndFlushSync, mkdirp } from 'vs/base/node/extfs';
import { FileStorage } from 'vs/platform/state/node/stateService';
suite('StateService', () => {
const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'stateservice');
const storageFile = path.join(parentDir, 'storage.json');
teardown(done => {
extfs.del(parentDir, os.tmpdir(), done);
});
test('Basics', done => {
return mkdirp(parentDir).then(() => {
writeFileAndFlushSync(storageFile, '');
let service = new FileStorage(storageFile, () => null);
service.setItem('some.key', 'some.value');
assert.equal(service.getItem('some.key'), 'some.value');
service.removeItem('some.key');
assert.equal(service.getItem('some.key', 'some.default'), 'some.default');
assert.ok(!service.getItem('some.unknonw.key'));
service.setItem('some.other.key', 'some.other.value');
service = new FileStorage(storageFile, () => null);
assert.equal(service.getItem('some.other.key'), 'some.other.value');
service.setItem('some.other.key', 'some.other.value');
assert.equal(service.getItem('some.other.key'), 'some.other.value');
service.setItem('some.undefined.key', void 0);
assert.equal(service.getItem('some.undefined.key', 'some.default'), 'some.default');
service.setItem('some.null.key', null);
assert.equal(service.getItem('some.null.key', 'some.default'), 'some.default');
done();
});
});
});

View File

@@ -13,7 +13,6 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag
export interface IStorage {
length: number;
key(index: number): string;
clear(): void;
setItem(key: string, value: any): void;
getItem(key: string): string;
removeItem(key: string): void;
@@ -23,11 +22,11 @@ export class StorageService implements IStorageService {
public _serviceBrand: any;
public static COMMON_PREFIX = 'storage://';
public static GLOBAL_PREFIX = `${StorageService.COMMON_PREFIX}global/`;
public static WORKSPACE_PREFIX = `${StorageService.COMMON_PREFIX}workspace/`;
public static WORKSPACE_IDENTIFIER = 'workspaceidentifier';
public static NO_WORKSPACE_IDENTIFIER = '__$noWorkspace__';
public static readonly COMMON_PREFIX = 'storage://';
public static readonly GLOBAL_PREFIX = `${StorageService.COMMON_PREFIX}global/`;
public static readonly WORKSPACE_PREFIX = `${StorageService.COMMON_PREFIX}workspace/`;
public static readonly WORKSPACE_IDENTIFIER = 'workspaceidentifier';
public static readonly NO_WORKSPACE_IDENTIFIER = '__$noWorkspace__';
private _workspaceStorage: IStorage;
private _globalStorage: IStorage;
@@ -122,11 +121,6 @@ export class StorageService implements IStorageService {
}
}
public clear(): void {
this._globalStorage.clear();
this._workspaceStorage.clear();
}
public store(key: string, value: any, scope = StorageScope.GLOBAL): void {
const storage = (scope === StorageScope.GLOBAL) ? this._globalStorage : this._workspaceStorage;
@@ -217,10 +211,6 @@ export class InMemoryLocalStorage implements IStorage {
return null;
}
public clear(): void {
this.store = {};
}
public setItem(key: string, value: any): void {
this.store[key] = value.toString();
}

View File

@@ -1,94 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as path from 'path';
import * as fs from 'original-fs';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const IStorageService = createDecorator<IStorageService>('storageService');
export interface IStorageService {
_serviceBrand: any;
getItem<T>(key: string, defaultValue?: T): T;
setItem(key: string, data: any): void;
removeItem(key: string): void;
}
export class StorageService implements IStorageService {
_serviceBrand: any;
private dbPath: string;
private database: any = null;
constructor( @IEnvironmentService private environmentService: IEnvironmentService) {
this.dbPath = path.join(environmentService.userDataPath, 'storage.json');
}
public getItem<T>(key: string, defaultValue?: T): T {
if (!this.database) {
this.database = this.load();
}
const res = this.database[key];
if (typeof res === 'undefined') {
return defaultValue;
}
return this.database[key];
}
public setItem(key: string, data: any): void {
if (!this.database) {
this.database = this.load();
}
// Shortcut for primitives that did not change
if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') {
if (this.database[key] === data) {
return;
}
}
this.database[key] = data;
this.save();
}
public removeItem(key: string): void {
if (!this.database) {
this.database = this.load();
}
if (this.database[key]) {
delete this.database[key];
this.save();
}
}
private load(): any {
try {
return JSON.parse(fs.readFileSync(this.dbPath).toString()); // invalid JSON or permission issue can happen here
} catch (error) {
if (this.environmentService.verbose) {
console.error(error);
}
return {};
}
}
private save(): void {
try {
fs.writeFileSync(this.dbPath, JSON.stringify(this.database, null, 4)); // permission issue can happen here
} catch (error) {
if (this.environmentService.verbose) {
console.error(error);
}
}
}
}

View File

@@ -13,25 +13,7 @@ import { StorageScope } from 'vs/platform/storage/common/storage';
import { startsWith } from 'vs/base/common/strings';
suite('Storage Migration', () => {
//slet storage = window.localStorage;
setup(() => {
//storage.clear();
});
teardown(() => {
//storage.clear();
});
test('Parse Storage (Global)', () => {
// const service = createService();
// const parsed = parseStorage(storage);
// assert.equal(parsed.global.size, 4);
// assert.equal(parsed.global.get('key1'), service.get('key1'));
// assert.equal(parsed.global.get('key2.something'), service.get('key2.something'));
// assert.equal(parsed.global.get('key3/special'), service.get('key3/special'));
// assert.equal(parsed.global.get('key4 space'), service.get('key4 space'));
});
});

View File

@@ -1,78 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TimeoutTimer } from 'vs/base/common/async';
import Event, { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import * as dom from 'vs/base/browser/dom';
export enum UserStatus {
Idle,
Active
}
export class IdleMonitor extends Disposable {
private _lastActiveTime: number;
private _idleCheckTimeout: TimeoutTimer;
private _status: UserStatus;
private _idleTime: number;
private _onStatusChange: Emitter<UserStatus>;
get onStatusChange(): Event<UserStatus> { return this._onStatusChange.event; }
constructor(idleTime: number) {
super();
this._status = null;
this._idleCheckTimeout = this._register(new TimeoutTimer());
this._lastActiveTime = -1;
this._idleTime = idleTime;
this._onStatusChange = new Emitter<UserStatus>();
this._register(dom.addDisposableListener(document, 'mousemove', () => this._onUserActive()));
this._register(dom.addDisposableListener(document, 'keydown', () => this._onUserActive()));
this._onUserActive();
}
get status(): UserStatus {
return this._status;
}
private _onUserActive(): void {
this._lastActiveTime = (new Date()).getTime();
if (this._status !== UserStatus.Active) {
this._status = UserStatus.Active;
this._scheduleIdleCheck();
this._onStatusChange.fire(this._status);
}
}
private _onUserIdle(): void {
if (this._status !== UserStatus.Idle) {
this._status = UserStatus.Idle;
this._onStatusChange.fire(this._status);
}
}
private _scheduleIdleCheck(): void {
const minimumTimeWhenUserCanBecomeIdle = this._lastActiveTime + this._idleTime;
const timeout = minimumTimeWhenUserCanBecomeIdle - (new Date()).getTime();
this._idleCheckTimeout.setIfNotSet(() => this._checkIfUserIsIdle(), timeout);
}
private _checkIfUserIsIdle(): void {
const actualIdleTime = (new Date()).getTime() - this._lastActiveTime;
if (actualIdleTime >= this._idleTime) {
this._onUserIdle();
} else {
this._scheduleIdleCheck();
}
}
}

View File

@@ -7,6 +7,7 @@
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { deepClone } from 'vs/base/common/objects';
/* __GDPR__FRAGMENT__
"IExperiments" : {
@@ -88,5 +89,5 @@ function splitRandom(random: number): [number, boolean] {
}
function getExperimentsOverrides(configurationService: IConfigurationService): IExperiments {
return configurationService.getConfiguration<any>('experiments') || {};
return deepClone(configurationService.getValue<any>('experiments')) || {};
}

View File

@@ -34,7 +34,10 @@ export class TelemetryAppenderClient implements ITelemetryAppender {
constructor(private channel: ITelemetryAppenderChannel) { }
log(eventName: string, data?: any): any {
return this.channel.call('log', { eventName, data });
this.channel.call('log', { eventName, data })
.done(null, err => `Failed to log telemetry: ${console.warn(err)}`);
return TPromise.as(null);
}
dispose(): any {

View File

@@ -75,7 +75,7 @@ export class TelemetryService implements ITelemetryService {
}
private _updateUserOptIn(): void {
const config = this._configurationService.getConfiguration<any>(TELEMETRY_SECTION_ID);
const config = this._configurationService.getValue<any>(TELEMETRY_SECTION_ID);
this._userOptIn = config ? config.enableTelemetry : this._userOptIn;
}

View File

@@ -11,17 +11,16 @@ import paths = require('vs/base/common/paths');
import URI from 'vs/base/common/uri';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { IKeybindingService, KeybindingSource } from 'vs/platform/keybinding/common/keybinding';
import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
import { ITelemetryService, ITelemetryInfo, ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
export const NullTelemetryService = {
_serviceBrand: undefined,
export const NullTelemetryService = new class implements ITelemetryService {
_serviceBrand: undefined;
publicLog(eventName: string, data?: ITelemetryData) {
return TPromise.as<void>(null);
},
isOptedIn: true,
return TPromise.wrap<void>(null);
}
isOptedIn: true;
getTelemetryInfo(): TPromise<ITelemetryInfo> {
return TPromise.as({
return TPromise.wrap({
instanceId: 'someValue.instanceId',
sessionId: 'someValue.sessionId',
machineId: 'someValue.machineId'
@@ -184,17 +183,6 @@ export function configurationTelemetry(telemetryService: ITelemetryService, conf
});
}
export function lifecycleTelemetry(telemetryService: ITelemetryService, lifecycleService: ILifecycleService): IDisposable {
return lifecycleService.onShutdown(event => {
/* __GDPR__
"shutdown" : {
"reason" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
telemetryService.publicLog('shutdown', { reason: ShutdownReason[event] });
});
}
export function keybindingsTelemetry(telemetryService: ITelemetryService, keybindingService: IKeybindingService): IDisposable {
return keybindingService.onDidUpdateKeybindings(event => {
if (event.source === KeybindingSource.User && event.keybindings) {

View File

@@ -7,16 +7,15 @@ import * as Platform from 'vs/base/common/platform';
import * as os from 'os';
import { TPromise } from 'vs/base/common/winjs.base';
import * as uuid from 'vs/base/common/uuid';
import { readFile } from 'vs/base/node/pfs';
// {{SQL CARBON EDIT}}
import product from 'vs/platform/node/product';
export const machineIdStorageKey = 'telemetry.machineId';
export const machineIdIpcChannel = 'vscode:machineId';
export function resolveCommonProperties(commit: string, version: string, source: string): TPromise<{ [name: string]: string; }> {
export function resolveCommonProperties(commit: string, version: string, machineId: string, installSourcePath: string): TPromise<{ [name: string]: string; }> {
const result: { [name: string]: string; } = Object.create(null);
// __GDPR__COMMON__ "common.machineId" : { "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight" }
result['common.machineId'] = machineId;
// __GDPR__COMMON__ "sessionID" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['sessionID'] = uuid.generateUuid() + Date.now();
// __GDPR__COMMON__ "commitHash" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
@@ -31,9 +30,7 @@ export function resolveCommonProperties(commit: string, version: string, source:
result['common.nodePlatform'] = process.platform;
// __GDPR__COMMON__ "common.nodeArch" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.nodeArch'] = process.arch;
// __GDPR__COMMON__ "common.source" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.source'] = source;
// {{SQL CARBON EDIT}}
result['common.application.name'] = product.nameLong;
@@ -58,5 +55,13 @@ export function resolveCommonProperties(commit: string, version: string, source:
}
});
return TPromise.as(result);
return readFile(installSourcePath, 'utf8').then(contents => {
// __GDPR__COMMON__ "common.source" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.source'] = contents.slice(0, 30);
return result;
}, error => {
return result;
});
}

View File

@@ -7,15 +7,14 @@ import * as os from 'os';
import { TPromise } from 'vs/base/common/winjs.base';
import * as uuid from 'vs/base/common/uuid';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { getMachineId } from 'vs/base/node/id';
import { resolveCommonProperties, machineIdStorageKey } from '../node/commonProperties';
import { resolveCommonProperties } from '../node/commonProperties';
// {{ SQL CARBON EDIT }}
import product from 'vs/platform/node/product';
import * as Utils from 'sql/common/telemetryUtilities';
export function resolveWorkbenchCommonProperties(storageService: IStorageService, commit: string, version: string, source: string): TPromise<{ [name: string]: string }> {
return resolveCommonProperties(commit, version, source).then(result => {
export function resolveWorkbenchCommonProperties(storageService: IStorageService, commit: string, version: string, machineId: string, installSourcePath: string): TPromise<{ [name: string]: string }> {
return resolveCommonProperties(commit, version, machineId, installSourcePath).then(result => {
// __GDPR__COMMON__ "common.version.shell" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.version.shell'] = process.versions && (<any>process).versions['electron'];
// __GDPR__COMMON__ "common.version.renderer" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
@@ -38,35 +37,19 @@ export function resolveWorkbenchCommonProperties(storageService: IStorageService
result['common.lastSessionDate'] = lastSessionDate;
// __GDPR__COMMON__ "common.isNewSession" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.isNewSession'] = !lastSessionDate ? '1' : '0';
const promises: TPromise<any>[] = [];
// __GDPR__COMMON__ "common.instanceId" : { "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight" }
promises.push(getOrCreateInstanceId(storageService).then(value => result['common.instanceId'] = value));
// __GDPR__COMMON__ "common.machineId" : { "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight" }
promises.push(getOrCreateMachineId(storageService).then(value => result['common.machineId'] = value));
result['common.instanceId'] = getOrCreateInstanceId(storageService);
return TPromise.join(promises).then(() => result);
});
}
function getOrCreateInstanceId(storageService: IStorageService): TPromise<string> {
let result = storageService.get('telemetry.instanceId') || uuid.generateUuid();
storageService.store('telemetry.instanceId', result);
return TPromise.as(result);
}
export function getOrCreateMachineId(storageService: IStorageService): TPromise<string> {
let result = storageService.get(machineIdStorageKey);
if (result) {
return TPromise.as(result);
}
return getMachineId().then(result => {
storageService.store(machineIdStorageKey, result);
return result;
});
}
function getOrCreateInstanceId(storageService: IStorageService): string {
const result = storageService.get('telemetry.instanceId') || uuid.generateUuid();
storageService.store('telemetry.instanceId', result);
return result;
}
// {{SQL CARBON EDIT}}
// Get the unique ID for the current user
function getUserId(storageService: IStorageService): Promise<string> {

View File

@@ -9,8 +9,8 @@ import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppen
interface IAppInsightsEvent {
eventName: string;
properties?: { string?: string; };
measurements?: { string?: number; };
properties?: { [x: string]: string; };
measurements?: { [x: string]: number; };
}
class AppInsightsMock {
@@ -34,7 +34,7 @@ class AppInsightsMock {
this.exceptions.push(exception);
}
public sendPendingData(callback): void {
public sendPendingData(_callback: any): void {
// called on dispose
}
}
@@ -74,18 +74,18 @@ suite('AIAdapter', () => {
test('property limits', () => {
var reallyLongPropertyName = 'abcdefghijklmnopqrstuvwxyz';
for (var i = 0; i < 6; i++) {
for (let i = 0; i < 6; i++) {
reallyLongPropertyName += 'abcdefghijklmnopqrstuvwxyz';
}
assert(reallyLongPropertyName.length > 150);
var reallyLongPropertyValue = 'abcdefghijklmnopqrstuvwxyz012345678901234567890123';
for (var i = 0; i < 21; i++) {
for (let i = 0; i < 21; i++) {
reallyLongPropertyValue += 'abcdefghijklmnopqrstuvwxyz012345678901234567890123';
}
assert(reallyLongPropertyValue.length > 1024);
var data = {};
var data = Object.create(null);
data[reallyLongPropertyName] = '1234';
data['reallyLongPropertyValue'] = reallyLongPropertyValue;
adapter.log('testEvent', data);

View File

@@ -5,40 +5,52 @@
'use strict';
import * as assert from 'assert';
import * as path from 'path';
import * as os from 'os';
import * as fs from 'fs';
import { TPromise } from 'vs/base/common/winjs.base';
import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/node/workbenchCommonProperties';
import { StorageService, InMemoryLocalStorage } from 'vs/platform/storage/common/storageService';
import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
import { getRandomTestPath } from 'vs/workbench/test/workbenchTestServices';
import { del } from 'vs/base/node/extfs';
import { mkdirp } from 'vs/base/node/pfs';
suite('Telemetry - common properties', function () {
const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'telemetryservice');
const installSource = path.join(parentDir, 'installSource');
const commit = void 0;
const version = void 0;
const source = void 0;
let storageService;
const commit: string = void 0;
const version: string = void 0;
let storageService: StorageService;
setup(() => {
storageService = new StorageService(new InMemoryLocalStorage(), null, TestWorkspace.id);
});
teardown(done => {
del(parentDir, os.tmpdir(), done);
});
test('default', function () {
return mkdirp(parentDir).then(() => {
fs.writeFileSync(installSource, 'my.install.source');
return resolveWorkbenchCommonProperties(storageService, commit, version, source).then(props => {
return resolveWorkbenchCommonProperties(storageService, commit, version, 'someMachineId', installSource).then(props => {
assert.ok('commitHash' in props);
assert.ok('sessionID' in props);
assert.ok('timestamp' in props);
assert.ok('common.platform' in props);
assert.ok('common.nodePlatform' in props);
assert.ok('common.nodeArch' in props);
assert.ok('common.timesincesessionstart' in props);
assert.ok('common.sequence' in props);
assert.ok('commitHash' in props);
assert.ok('sessionID' in props);
assert.ok('timestamp' in props);
assert.ok('common.platform' in props);
assert.ok('common.nodePlatform' in props);
assert.ok('common.nodeArch' in props);
assert.ok('common.timesincesessionstart' in props);
assert.ok('common.sequence' in props);
// assert.ok('common.version.shell' in first.data); // only when running on electron
// assert.ok('common.version.renderer' in first.data);
assert.ok('common.osVersion' in props, 'osVersion');
assert.ok('version' in props);
assert.ok('common.source' in props);
// assert.ok('common.version.shell' in first.data); // only when running on electron
// assert.ok('common.version.renderer' in first.data);
assert.ok('common.osVersion' in props, 'osVersion');
assert.ok('version' in props);
assert.equal(props['common.source'], 'my.install.source');
// {{SQL CARBON EDIT}}
assert.ok('common.application.name' in props);
@@ -47,10 +59,16 @@ suite('Telemetry - common properties', function () {
assert.ok('common.lastSessionDate' in props, 'lastSessionDate'); // conditional, see below, 'lastSessionDate'ow
assert.ok('common.isNewSession' in props, 'isNewSession');
// machine id et al
assert.ok('common.instanceId' in props, 'instanceId');
assert.ok('common.machineId' in props, 'machineId');
// machine id et al
assert.ok('common.instanceId' in props, 'instanceId');
assert.ok('common.machineId' in props, 'machineId');
fs.unlinkSync(installSource);
return resolveWorkbenchCommonProperties(storageService, commit, version, 'someMachineId', installSource).then(props => {
assert.ok(!('common.source' in props));
});
});
});
});
@@ -58,7 +76,7 @@ suite('Telemetry - common properties', function () {
storageService.store('telemetry.lastSessionDate', new Date().toUTCString());
return resolveWorkbenchCommonProperties(storageService, commit, version, source).then(props => {
return resolveWorkbenchCommonProperties(storageService, commit, version, 'someMachineId', installSource).then(props => {
assert.ok('common.lastSessionDate' in props); // conditional, see below
assert.ok('common.isNewSession' in props);
@@ -67,7 +85,7 @@ suite('Telemetry - common properties', function () {
});
test('values chance on ask', function () {
return resolveWorkbenchCommonProperties(storageService, commit, version, source).then(props => {
return resolveWorkbenchCommonProperties(storageService, commit, version, 'someMachineId', installSource).then(props => {
let value1 = props['common.sequence'];
let value2 = props['common.sequence'];
assert.ok(value1 !== value2, 'seq');

View File

@@ -40,16 +40,16 @@ class TestTelemetryAppender implements ITelemetryAppender {
}
class ErrorTestingSettings {
public personalInfo;
public importantInfo;
public filePrefix;
public dangerousPathWithoutImportantInfo;
public dangerousPathWithImportantInfo;
public missingModelPrefix;
public missingModelMessage;
public noSuchFilePrefix;
public noSuchFileMessage;
public stack;
public personalInfo: string;
public importantInfo: string;
public filePrefix: string;
public dangerousPathWithoutImportantInfo: string;
public dangerousPathWithImportantInfo: string;
public missingModelPrefix: string;
public missingModelMessage: string;
public noSuchFilePrefix: string;
public noSuchFileMessage: string;
public stack: string[];
constructor() {
this.personalInfo = 'DANGEROUS/PATH';
@@ -203,7 +203,7 @@ suite('TelemetryService', () => {
});
}));
test('Error events', sinon.test(function () {
test('Error events', sinon.test(function (this: any) {
let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler();
Errors.setUnexpectedErrorHandler(() => { });
@@ -262,7 +262,7 @@ suite('TelemetryService', () => {
// }
// }));
test('Handle global errors', sinon.test(function () {
test('Handle global errors', sinon.test(function (this: any) {
let errorStub = sinon.stub();
window.onerror = errorStub;
@@ -289,7 +289,7 @@ suite('TelemetryService', () => {
service.dispose();
}));
test('Uncaught Error Telemetry removes PII from filename', sinon.test(function () {
test('Uncaught Error Telemetry removes PII from filename', sinon.test(function (this: any) {
let errorStub = sinon.stub();
window.onerror = errorStub;
let settings = new ErrorTestingSettings();
@@ -318,7 +318,7 @@ suite('TelemetryService', () => {
service.dispose();
}));
test('Unexpected Error Telemetry removes PII', sinon.test(function () {
test('Unexpected Error Telemetry removes PII', sinon.test(function (this: any) {
let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler();
Errors.setUnexpectedErrorHandler(() => { });
try {
@@ -348,7 +348,7 @@ suite('TelemetryService', () => {
}
}));
test('Uncaught Error Telemetry removes PII', sinon.test(function () {
test('Uncaught Error Telemetry removes PII', sinon.test(function (this: any) {
let errorStub = sinon.stub();
window.onerror = errorStub;
let settings = new ErrorTestingSettings();
@@ -374,7 +374,7 @@ suite('TelemetryService', () => {
service.dispose();
}));
test('Unexpected Error Telemetry removes PII but preserves Code file path', sinon.test(function () {
test('Unexpected Error Telemetry removes PII but preserves Code file path', sinon.test(function (this: any) {
let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler();
Errors.setUnexpectedErrorHandler(() => { });
@@ -409,7 +409,7 @@ suite('TelemetryService', () => {
}
}));
test('Uncaught Error Telemetry removes PII but preserves Code file path', sinon.test(function () {
test('Uncaught Error Telemetry removes PII but preserves Code file path', sinon.test(function (this: any) {
let errorStub = sinon.stub();
window.onerror = errorStub;
let settings = new ErrorTestingSettings();
@@ -437,7 +437,7 @@ suite('TelemetryService', () => {
service.dispose();
}));
test('Unexpected Error Telemetry removes PII but preserves Code file path when PIIPath is configured', sinon.test(function () {
test('Unexpected Error Telemetry removes PII but preserves Code file path when PIIPath is configured', sinon.test(function (this: any) {
let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler();
Errors.setUnexpectedErrorHandler(() => { });
@@ -472,7 +472,7 @@ suite('TelemetryService', () => {
}
}));
test('Uncaught Error Telemetry removes PII but preserves Code file path when PIIPath is configured', sinon.test(function () {
test('Uncaught Error Telemetry removes PII but preserves Code file path when PIIPath is configured', sinon.test(function (this: any) {
let errorStub = sinon.stub();
window.onerror = errorStub;
let settings = new ErrorTestingSettings();
@@ -500,7 +500,7 @@ suite('TelemetryService', () => {
service.dispose();
}));
test('Unexpected Error Telemetry removes PII but preserves Missing Model error message', sinon.test(function () {
test('Unexpected Error Telemetry removes PII but preserves Missing Model error message', sinon.test(function (this: any) {
let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler();
Errors.setUnexpectedErrorHandler(() => { });
@@ -535,7 +535,7 @@ suite('TelemetryService', () => {
}
}));
test('Uncaught Error Telemetry removes PII but preserves Missing Model error message', sinon.test(function () {
test('Uncaught Error Telemetry removes PII but preserves Missing Model error message', sinon.test(function (this: any) {
let errorStub = sinon.stub();
window.onerror = errorStub;
let settings = new ErrorTestingSettings();
@@ -564,7 +564,7 @@ suite('TelemetryService', () => {
service.dispose();
}));
test('Unexpected Error Telemetry removes PII but preserves No Such File error message', sinon.test(function () {
test('Unexpected Error Telemetry removes PII but preserves No Such File error message', sinon.test(function (this: any) {
let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler();
Errors.setUnexpectedErrorHandler(() => { });
@@ -599,7 +599,7 @@ suite('TelemetryService', () => {
}
}));
test('Uncaught Error Telemetry removes PII but preserves No Such File error message', sinon.test(function () {
test('Uncaught Error Telemetry removes PII but preserves No Such File error message', sinon.test(function (this: any) {
let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler();
Errors.setUnexpectedErrorHandler(() => { });
@@ -676,29 +676,26 @@ suite('TelemetryService', () => {
appender: testAppender
}, {
_serviceBrand: undefined,
getConfiguration() {
getValue() {
return {
enableTelemetry: enableTelemetry
} as any;
},
getValue(key) {
return getConfigurationValue(this.getConfiguration(), key);
},
updateValue() {
updateValue(): TPromise<void> {
return null;
},
inspect(key: string) {
return {
value: getConfigurationValue(this.getConfiguration(), key),
default: getConfigurationValue(this.getConfiguration(), key),
user: getConfigurationValue(this.getConfiguration(), key),
value: getConfigurationValue(this.getValue(), key),
default: getConfigurationValue(this.getValue(), key),
user: getConfigurationValue(this.getValue(), key),
workspace: null,
workspaceFolder: null
};
},
keys() { return { default: [], user: [], workspace: [], workspaceFolder: [] }; },
onDidChangeConfiguration: emitter.event,
reloadConfiguration() { return null; },
reloadConfiguration(): TPromise<void> { return null; },
getConfigurationData() { return null; }
});

View File

@@ -133,7 +133,7 @@ export interface IQuickOpenStyleOverrides extends IListStyleOverrides, IInputBox
widgetShadow?: ColorIdentifier;
pickerGroupForeground?: ColorIdentifier;
pickerGroupBorder?: ColorIdentifier;
};
}
export function attachQuickOpenStyler(widget: IThemable, themeService: IThemeService, style?: IQuickOpenStyleOverrides): IDisposable {
return attachStyler(themeService, {

View File

@@ -5,7 +5,7 @@
'use strict';
import Event from 'vs/base/common/event';
import Event, { NodeEventEmitter } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { TPromise } from 'vs/base/common/winjs.base';
@@ -35,7 +35,7 @@ export interface IUpdate {
url?: string;
}
export interface IAutoUpdater extends NodeJS.EventEmitter {
export interface IAutoUpdater extends NodeEventEmitter {
setFeedURL(url: string): void;
checkForUpdates(): void;
quitAndInstall(): void;

View File

@@ -63,7 +63,7 @@ export class UpdateChannelClient implements IUpdateService {
get onStateChange(): Event<State> { return this._onStateChange.event; }
private _state: State = State.Uninitialized;
get state(): State { return this._state; };
get state(): State { return this._state; }
constructor(private channel: IUpdateChannel) {
// always set this._state as the state changes

View File

@@ -11,7 +11,6 @@ import { checksum } from 'vs/base/node/crypto';
import { EventEmitter } from 'events';
import { tmpdir } from 'os';
import { spawn } from 'child_process';
import { mkdirp } from 'vs/base/node/extfs';
import { isString } from 'vs/base/common/types';
import { Promise, TPromise } from 'vs/base/common/winjs.base';
import { download, asJson } from 'vs/base/node/request';
@@ -43,7 +42,7 @@ export class Win32AutoUpdaterImpl extends EventEmitter implements IAutoUpdater {
get cachePath(): TPromise<string> {
// {{SQL CARBON EDIT}}
const result = path.join(tmpdir(), `sqlops-update-${process.arch}`);
return new TPromise<string>((c, e) => mkdirp(result, null, err => err ? e(err) : c(result)));
return pfs.mkdirp(result, null).then(() => result);
}
setFeedURL(url: string): void {

View File

@@ -9,10 +9,9 @@ import * as fs from 'original-fs';
import * as path from 'path';
import * as electron from 'electron';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import Event, { Emitter, once, filterEvent } from 'vs/base/common/event';
import Event, { Emitter, once, filterEvent, fromNodeEventEmitter } from 'vs/base/common/event';
import { always, Throttler } from 'vs/base/common/async';
import { memoize } from 'vs/base/common/decorators';
import { fromEventEmitter } from 'vs/base/node/event';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Win32AutoUpdaterImpl } from './auto-updater.win32';
import { LinuxAutoUpdaterImpl } from './auto-updater.linux';
@@ -23,6 +22,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { IUpdateService, State, IAutoUpdater, IUpdate, IRawUpdate } from 'vs/platform/update/common/update';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ILogService } from 'vs/platform/log/common/log';
export class UpdateService implements IUpdateService {
@@ -53,22 +53,22 @@ export class UpdateService implements IUpdateService {
@memoize
private get onRawError(): Event<string> {
return fromEventEmitter(this.raw, 'error', (_, message) => message);
return fromNodeEventEmitter(this.raw, 'error', (_, message) => message);
}
@memoize
private get onRawUpdateNotAvailable(): Event<void> {
return fromEventEmitter<void>(this.raw, 'update-not-available');
return fromNodeEventEmitter<void>(this.raw, 'update-not-available');
}
@memoize
private get onRawUpdateAvailable(): Event<{ url: string; version: string; }> {
return filterEvent(fromEventEmitter(this.raw, 'update-available', (_, url, version) => ({ url, version })), ({ url }) => !!url);
return filterEvent(fromNodeEventEmitter(this.raw, 'update-available', (_, url, version) => ({ url, version })), ({ url }) => !!url);
}
@memoize
private get onRawUpdateDownloaded(): Event<IRawUpdate> {
return fromEventEmitter(this.raw, 'update-downloaded', (_, releaseNotes, version, date, url) => ({ releaseNotes, version, date }));
return fromNodeEventEmitter(this.raw, 'update-downloaded', (_, releaseNotes, version, date, url) => ({ releaseNotes, version, date }));
}
get state(): State {
@@ -89,7 +89,8 @@ export class UpdateService implements IUpdateService {
@ILifecycleService private lifecycleService: ILifecycleService,
@IConfigurationService private configurationService: IConfigurationService,
@ITelemetryService private telemetryService: ITelemetryService,
@IEnvironmentService private environmentService: IEnvironmentService
@IEnvironmentService private environmentService: IEnvironmentService,
@ILogService private logService: ILogService
) {
if (process.platform === 'win32') {
this.raw = new Win32AutoUpdaterImpl(requestService);
@@ -122,7 +123,7 @@ export class UpdateService implements IUpdateService {
// Start checking for updates after 30 seconds
this.scheduleCheckForUpdates(30 * 1000)
.done(null, err => console.error(err));
.done(null, err => this.logService.error(err));
}
private scheduleCheckForUpdates(delay = 60 * 60 * 1000): TPromise<void> {
@@ -225,9 +226,7 @@ export class UpdateService implements IUpdateService {
}
private getUpdateChannel(): string {
const config = this.configurationService.getConfiguration<{ channel: string; }>('update');
const channel = config && config.channel;
const channel = this.configurationService.getValue<string>('update.channel');
return channel === 'none' ? null : product.quality;
}
@@ -271,7 +270,10 @@ export class UpdateService implements IUpdateService {
return TPromise.as(null);
}
this.logService.trace('update#quitAndInstall(): before lifecycle quit()');
this.lifecycleService.quit(true /* from update */).done(vetod => {
this.logService.trace(`update#quitAndInstall(): after lifecycle quit() with veto: ${vetod}`);
if (vetod) {
return;
}
@@ -280,9 +282,11 @@ export class UpdateService implements IUpdateService {
// we workaround this issue by forcing an explicit flush of the storage data.
// see also https://github.com/Microsoft/vscode/issues/172
if (process.platform === 'darwin') {
this.logService.trace('update#quitAndInstall(): calling flushStorageData()');
electron.session.defaultSession.flushStorageData();
}
this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()');
this.raw.quitAndInstall();
});

View File

@@ -5,8 +5,7 @@
'use strict';
import Event, { mapEvent, chain, echo, Emitter, anyEvent } from 'vs/base/common/event';
import { fromEventEmitter } from 'vs/base/node/event';
import Event, { mapEvent, chain, echo, Emitter, anyEvent, fromNodeEventEmitter } from 'vs/base/common/event';
import { IURLService } from 'vs/platform/url/common/url';
import product from 'vs/platform/node/product';
import { app } from 'electron';
@@ -28,7 +27,7 @@ export class URLService implements IURLService {
app.setAsDefaultProtocolClient(product.urlProtocol, process.execPath, ['--open-url', '--']);
const rawOnOpenUrl = fromEventEmitter(app, 'open-url', (event: Electron.Event, url: string) => ({ event, url }));
const rawOnOpenUrl = fromNodeEventEmitter(app, 'open-url', (event: Electron.Event, url: string) => ({ event, url }));
// always prevent default and return the url as string
const preventedOnOpenUrl = mapEvent(rawOnOpenUrl, ({ event, url }) => {

View File

@@ -14,6 +14,7 @@ import { ParsedArgs } from 'vs/platform/environment/common/environment';
import { IWorkspaceIdentifier, IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces';
import { IRecentlyOpened } from 'vs/platform/history/common/history';
import { ICommandAction } from 'vs/platform/actions/common/actions';
import { PerformanceEntry } from 'vs/base/common/performance';
export const IWindowsService = createDecorator<IWindowsService>('windowsService');
@@ -21,7 +22,7 @@ export interface INativeOpenDialogOptions {
windowId?: number;
forceNewWindow?: boolean;
dialogOptions?: Electron.OpenDialogOptions;
dialogOptions?: OpenDialogOptions;
telemetryEventName?: string;
telemetryExtraData?: ITelemetryData;
@@ -32,6 +33,63 @@ export interface IEnterWorkspaceResult {
backupPath: string;
}
export interface CrashReporterStartOptions {
companyName?: string;
submitURL: string;
productName?: string;
uploadToServer?: boolean;
ignoreSystemCrashHandler?: boolean;
extra?: any;
crashesDirectory?: string;
}
export interface OpenDialogOptions {
title?: string;
defaultPath?: string;
buttonLabel?: string;
filters?: FileFilter[];
properties?: Array<'openFile' | 'openDirectory' | 'multiSelections' | 'showHiddenFiles' | 'createDirectory' | 'promptToCreate' | 'noResolveAliases' | 'treatPackageAsDirectory'>;
message?: string;
}
export interface FileFilter {
extensions: string[];
name: string;
}
export interface MessageBoxOptions {
type?: string;
buttons?: string[];
defaultId?: number;
title?: string;
message: string;
detail?: string;
checkboxLabel?: string;
checkboxChecked?: boolean;
cancelId?: number;
noLink?: boolean;
normalizeAccessKeys?: boolean;
}
export interface SaveDialogOptions {
title?: string;
defaultPath?: string;
buttonLabel?: string;
filters?: FileFilter[];
message?: string;
nameFieldLabel?: string;
showsTagField?: boolean;
}
export interface OpenDialogOptions {
title?: string;
defaultPath?: string;
buttonLabel?: string;
filters?: FileFilter[];
properties?: Array<'openFile' | 'openDirectory' | 'multiSelections' | 'showHiddenFiles' | 'createDirectory' | 'promptToCreate' | 'noResolveAliases' | 'treatPackageAsDirectory'>;
message?: string;
}
export interface IWindowsService {
_serviceBrand: any;
@@ -95,7 +153,7 @@ export interface IWindowsService {
openExternal(url: string): TPromise<boolean>;
// TODO: this is a bit backwards
startCrashReporter(config: Electron.CrashReporterStartOptions): TPromise<void>;
startCrashReporter(config: CrashReporterStartOptions): TPromise<void>;
}
export const IWindowService = createDecorator<IWindowService>('windowService');
@@ -111,6 +169,7 @@ export interface IWindowService {
onDidChangeFocus: Event<boolean>;
getConfiguration(): IWindowConfiguration;
getCurrentWindowId(): number;
pickFileFolderAndOpen(options: INativeOpenDialogOptions): TPromise<void>;
pickFileAndOpen(options: INativeOpenDialogOptions): TPromise<void>;
@@ -130,15 +189,12 @@ export interface IWindowService {
closeWindow(): TPromise<void>;
isFocused(): TPromise<boolean>;
setDocumentEdited(flag: boolean): TPromise<void>;
isMaximized(): TPromise<boolean>;
maximizeWindow(): TPromise<void>;
unmaximizeWindow(): TPromise<void>;
onWindowTitleDoubleClick(): TPromise<void>;
show(): TPromise<void>;
showMessageBoxSync(options: Electron.MessageBoxOptions): number;
showMessageBox(options: Electron.MessageBoxOptions): TPromise<IMessageBoxResult>;
showSaveDialog(options: Electron.SaveDialogOptions, callback?: (fileName: string) => void): string;
showOpenDialog(options: Electron.OpenDialogOptions, callback?: (fileNames: string[]) => void): string[];
showMessageBox(options: MessageBoxOptions): number;
showSaveDialog(options: SaveDialogOptions): string;
showOpenDialog(options: OpenDialogOptions): string[];
showMessageBoxWithCheckbox(options: MessageBoxOptions): TPromise<IMessageBoxResult>;
}
export type MenuBarVisibility = 'default' | 'visible' | 'toggle' | 'hidden';
@@ -236,6 +292,8 @@ export interface IAddFoldersRequest {
}
export interface IWindowConfiguration extends ParsedArgs, IOpenFileRequest {
machineId: string;
appRoot: string;
execPath: string;
isInitialStartup?: boolean;
@@ -255,6 +313,7 @@ export interface IWindowConfiguration extends ParsedArgs, IOpenFileRequest {
backgroundColor?: string;
accessibilitySupport?: boolean;
perfEntries: PerformanceEntry[];
perfStartTime?: number;
perfAppReady?: number;
perfWindowLoadTime?: number;
@@ -263,4 +322,4 @@ export interface IWindowConfiguration extends ParsedArgs, IOpenFileRequest {
export interface IRunActionInWindowRequest {
id: string;
from: 'menu' | 'touchbar' | 'mouse';
}
}

View File

@@ -8,7 +8,7 @@
import { TPromise } from 'vs/base/common/winjs.base';
import Event, { buffer } from 'vs/base/common/event';
import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc';
import { IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult } from './windows';
import { IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult, CrashReporterStartOptions } from 'vs/platform/windows/common/windows';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces';
import { IRecentlyOpened } from 'vs/platform/history/common/history';
import { ICommandAction } from 'vs/platform/actions/common/actions';
@@ -59,7 +59,7 @@ export interface IWindowsChannel extends IChannel {
call(command: 'log', arg: [string, string[]]): TPromise<void>;
call(command: 'showItemInFolder', arg: string): TPromise<void>;
call(command: 'openExternal', arg: string): TPromise<boolean>;
call(command: 'startCrashReporter', arg: Electron.CrashReporterStartOptions): TPromise<void>;
call(command: 'startCrashReporter', arg: CrashReporterStartOptions): TPromise<void>;
call(command: string, arg?: any): TPromise<any>;
}
@@ -101,7 +101,7 @@ export class WindowsChannel implements IWindowsChannel {
}
return this.service.createAndEnterWorkspace(arg[0], folders, arg[2]);
};
}
case 'saveAndEnterWorkspace': return this.service.saveAndEnterWorkspace(arg[0], arg[1]);
case 'toggleFullScreen': return this.service.toggleFullScreen(arg);
case 'setRepresentedFilename': return this.service.setRepresentedFilename(arg[0], arg[1]);
@@ -320,7 +320,7 @@ export class WindowsChannelClient implements IWindowsService {
return this.channel.call('openExternal', url);
}
startCrashReporter(config: Electron.CrashReporterStartOptions): TPromise<void> {
startCrashReporter(config: CrashReporterStartOptions): TPromise<void> {
return this.channel.call('startCrashReporter', config);
}

View File

@@ -7,7 +7,7 @@
import Event, { filterEvent, mapEvent, anyEvent } from 'vs/base/common/event';
import { TPromise } from 'vs/base/common/winjs.base';
import { IWindowService, IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult } from 'vs/platform/windows/common/windows';
import { IWindowService, IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { remote } from 'electron';
import { IRecentlyOpened } from 'vs/platform/history/common/history';
import { ICommandAction } from 'vs/platform/actions/common/actions';
@@ -23,6 +23,7 @@ export class WindowService implements IWindowService {
constructor(
private windowId: number,
private configuration: IWindowConfiguration,
@IWindowsService private windowsService: IWindowsService
) {
const onThisWindowFocus = mapEvent(filterEvent(windowsService.onWindowFocus, id => id === windowId), _ => true);
@@ -34,6 +35,10 @@ export class WindowService implements IWindowService {
return this.windowId;
}
getConfiguration(): IWindowConfiguration {
return this.configuration;
}
pickFileFolderAndOpen(options: INativeOpenDialogOptions): TPromise<void> {
options.windowId = this.windowId;
@@ -106,18 +111,6 @@ export class WindowService implements IWindowService {
return this.windowsService.isFocused(this.windowId);
}
isMaximized(): TPromise<boolean> {
return this.windowsService.isMaximized(this.windowId);
}
maximizeWindow(): TPromise<void> {
return this.windowsService.maximizeWindow(this.windowId);
}
unmaximizeWindow(): TPromise<void> {
return this.windowsService.unmaximizeWindow(this.windowId);
}
onWindowTitleDoubleClick(): TPromise<void> {
return this.windowsService.onWindowTitleDoubleClick(this.windowId);
}
@@ -130,11 +123,11 @@ export class WindowService implements IWindowService {
return this.windowsService.showWindow(this.windowId);
}
showMessageBoxSync(options: Electron.MessageBoxOptions): number {
showMessageBox(options: Electron.MessageBoxOptions): number {
return remote.dialog.showMessageBox(remote.getCurrentWindow(), options);
}
showMessageBox(options: Electron.MessageBoxOptions): TPromise<IMessageBoxResult> {
showMessageBoxWithCheckbox(options: Electron.MessageBoxOptions): TPromise<IMessageBoxResult> {
return new TPromise((c, e) => {
return remote.dialog.showMessageBox(remote.getCurrentWindow(), options, (response: number, checkboxChecked: boolean) => {
c({ button: response, checkboxChecked });
@@ -142,7 +135,7 @@ export class WindowService implements IWindowService {
});
}
showSaveDialog(options: Electron.SaveDialogOptions, callback?: (fileName: string) => void): string {
showSaveDialog(options: Electron.SaveDialogOptions): string {
function normalizePath(path: string): string {
if (path && isMacintosh) {
@@ -152,14 +145,10 @@ export class WindowService implements IWindowService {
return path;
}
if (callback) {
return remote.dialog.showSaveDialog(remote.getCurrentWindow(), options, path => callback(normalizePath(path)));
}
return normalizePath(remote.dialog.showSaveDialog(remote.getCurrentWindow(), options)); // https://github.com/electron/electron/issues/4936
}
showOpenDialog(options: Electron.OpenDialogOptions, callback?: (fileNames: string[]) => void): string[] {
showOpenDialog(options: Electron.OpenDialogOptions): string[] {
function normalizePaths(paths: string[]): string[] {
if (paths && paths.length > 0 && isMacintosh) {
@@ -169,10 +158,6 @@ export class WindowService implements IWindowService {
return paths;
}
if (callback) {
return remote.dialog.showOpenDialog(remote.getCurrentWindow(), options, paths => callback(normalizePaths(paths)));
}
return normalizePaths(remote.dialog.showOpenDialog(remote.getCurrentWindow(), options)); // https://github.com/electron/electron/issues/4936
}

View File

@@ -12,8 +12,7 @@ import URI from 'vs/base/common/uri';
import { IWindowsService, OpenContext, INativeOpenDialogOptions, IEnterWorkspaceResult } from 'vs/platform/windows/common/windows';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { shell, crashReporter, app, Menu } from 'electron';
import Event, { chain } from 'vs/base/common/event';
import { fromEventEmitter } from 'vs/base/node/event';
import Event, { chain, fromNodeEventEmitter } from 'vs/base/common/event';
import { IURLService } from 'vs/platform/url/common/url';
import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
import { IWindowsMainService, ISharedProcess } from 'vs/platform/windows/electron-main/windows';
@@ -27,9 +26,9 @@ export class WindowsService implements IWindowsService, IDisposable {
private disposables: IDisposable[] = [];
readonly onWindowOpen: Event<number> = fromEventEmitter(app, 'browser-window-created', (_, w: Electron.BrowserWindow) => w.id);
readonly onWindowFocus: Event<number> = fromEventEmitter(app, 'browser-window-focus', (_, w: Electron.BrowserWindow) => w.id);
readonly onWindowBlur: Event<number> = fromEventEmitter(app, 'browser-window-blur', (_, w: Electron.BrowserWindow) => w.id);
readonly onWindowOpen: Event<number> = fromNodeEventEmitter(app, 'browser-window-created', (_, w: Electron.BrowserWindow) => w.id);
readonly onWindowFocus: Event<number> = fromNodeEventEmitter(app, 'browser-window-focus', (_, w: Electron.BrowserWindow) => w.id);
readonly onWindowBlur: Event<number> = fromNodeEventEmitter(app, 'browser-window-blur', (_, w: Electron.BrowserWindow) => w.id);
constructor(
private sharedProcess: ISharedProcess,

View File

@@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
export const InputFocusedContextKey = 'inputFocus';
export const InputFocusedContext = new RawContextKey<boolean>(InputFocusedContextKey, false);

View File

@@ -92,7 +92,6 @@ export interface IWorkspacesMainService extends IWorkspacesService {
createWorkspaceSync(folders?: IWorkspaceFolderCreationData[]): IWorkspaceIdentifier;
resolveWorkspace(path: string): TPromise<IResolvedWorkspace>;
resolveWorkspaceSync(path: string): IResolvedWorkspace;
isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean;

View File

@@ -7,7 +7,7 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IWorkspacesService, IWorkspaceIdentifier, IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspacesService, IWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
import URI from 'vs/base/common/uri';
export interface IWorkspacesChannel extends IChannel {
@@ -17,7 +17,7 @@ export interface IWorkspacesChannel extends IChannel {
export class WorkspacesChannel implements IWorkspacesChannel {
constructor(private service: IWorkspacesService) { }
constructor(private service: IWorkspacesMainService) { }
public call(command: string, arg?: any): TPromise<any> {
switch (command) {
@@ -34,7 +34,7 @@ export class WorkspacesChannel implements IWorkspacesChannel {
}
return this.service.createWorkspace(folders);
};
}
}
return void 0;

View File

@@ -11,9 +11,9 @@ import { isParent } from 'vs/platform/files/common/files';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { extname, join, dirname, isAbsolute, resolve } from 'path';
import { mkdirp, writeFile, readFile } from 'vs/base/node/pfs';
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { readFileSync, existsSync, mkdirSync } from 'fs';
import { isLinux, isMacintosh } from 'vs/base/common/platform';
import { delSync, readdirSync } from 'vs/base/node/extfs';
import { delSync, readdirSync, writeFileAndFlushSync } from 'vs/base/node/extfs';
import Event, { Emitter } from 'vs/base/common/event';
import { ILogService } from 'vs/platform/log/common/log';
import { isEqual } from 'vs/base/common/paths';
@@ -57,14 +57,6 @@ export class WorkspacesMainService implements IWorkspacesMainService {
return this._onUntitledWorkspaceDeleted.event;
}
public resolveWorkspace(path: string): TPromise<IResolvedWorkspace> {
if (!this.isWorkspacePath(path)) {
return TPromise.as(null); // does not look like a valid workspace config file
}
return readFile(path).then(contents => this.doResolveWorkspace(path, contents.toString()));
}
public resolveWorkspaceSync(path: string): IResolvedWorkspace {
if (!this.isWorkspacePath(path)) {
return null; // does not look like a valid workspace config file
@@ -94,7 +86,7 @@ export class WorkspacesMainService implements IWorkspacesMainService {
folders: toWorkspaceFolders(workspace.folders, URI.file(dirname(path)))
};
} catch (error) {
this.logService.log(error.toString());
this.logService.warn(error.toString());
}
return null;
@@ -144,7 +136,7 @@ export class WorkspacesMainService implements IWorkspacesMainService {
mkdirSync(configParent);
writeFileSync(workspace.configPath, JSON.stringify(storedWorkspace, null, '\t'));
writeFileAndFlushSync(workspace.configPath, JSON.stringify(storedWorkspace, null, '\t'));
return workspace;
}
@@ -270,7 +262,7 @@ export class WorkspacesMainService implements IWorkspacesMainService {
try {
delSync(dirname(configPath));
} catch (error) {
this.logService.log(`Unable to delete untitled workspace ${configPath} (${error}).`);
this.logService.warn(`Unable to delete untitled workspace ${configPath} (${error}).`);
}
}
@@ -279,7 +271,9 @@ export class WorkspacesMainService implements IWorkspacesMainService {
try {
untitledWorkspacePaths = readdirSync(this.workspacesHome).map(folder => join(this.workspacesHome, folder, UNTITLED_WORKSPACE_NAME));
} catch (error) {
this.logService.log(`Unable to read folders in ${this.workspacesHome} (${error}).`);
if (error && error.code !== 'ENOENT') {
this.logService.warn(`Unable to read folders in ${this.workspacesHome} (${error}).`);
}
}
const untitledWorkspaces: IWorkspaceIdentifier[] = coalesce(untitledWorkspacePaths.map(untitledWorkspacePath => {

View File

@@ -15,7 +15,7 @@ import { EnvironmentService } from 'vs/platform/environment/node/environmentServ
import { parseArgs } from 'vs/platform/environment/node/argv';
import { WorkspacesMainService, IStoredWorkspace } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { WORKSPACE_EXTENSION, IWorkspaceSavedEvent, IWorkspaceIdentifier, IRawFileWorkspaceFolder, IWorkspaceFolderCreationData, IRawUriWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import { LogMainService } from 'vs/platform/log/common/log';
import { ConsoleLogMainService } from 'vs/platform/log/common/log';
import URI from 'vs/base/common/uri';
import { getRandomTestPath } from 'vs/workbench/test/workbenchTestServices';
@@ -48,7 +48,7 @@ suite('WorkspacesMainService', () => {
}
const environmentService = new TestEnvironmentService(parseArgs(process.argv), process.execPath);
const logService = new LogMainService(environmentService);
const logService = new ConsoleLogMainService(environmentService);
let service: TestWorkspacesMainService;
@@ -186,32 +186,6 @@ suite('WorkspacesMainService', () => {
});
});
test('resolveWorkspace', done => {
return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => {
return service.resolveWorkspace(workspace.configPath).then(ws => {
assert.ok(ws);
// make it a valid workspace path
const newPath = path.join(path.dirname(workspace.configPath), `workspace.${WORKSPACE_EXTENSION}`);
fs.renameSync(workspace.configPath, newPath);
workspace.configPath = newPath;
return service.resolveWorkspace(workspace.configPath).then(resolved => {
assert.equal(2, resolved.folders.length);
assert.equal(resolved.configPath, workspace.configPath);
assert.ok(resolved.id);
fs.writeFileSync(workspace.configPath, JSON.stringify({ something: 'something' })); // invalid workspace
return service.resolveWorkspace(workspace.configPath).then(resolvedInvalid => {
assert.ok(!resolvedInvalid);
done();
});
});
});
});
});
test('resolveWorkspaceSync (support relative paths)', done => {
return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => {
fs.writeFileSync(workspace.configPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib' }] }));