mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
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:
@@ -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();
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()}`);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
44
src/vs/platform/configuration/node/configuration.ts
Normal file
44
src/vs/platform/configuration/node/configuration.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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')));
|
||||
});
|
||||
});
|
||||
@@ -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),
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)}`;
|
||||
}
|
||||
|
||||
@@ -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'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -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}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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' })));
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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',
|
||||
|
||||
10
src/vs/platform/files/node/files.ts
Normal file
10
src/vs/platform/files/node/files.ts
Normal 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;
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -22,7 +22,7 @@ interface IStorageData {
|
||||
}
|
||||
|
||||
class IntegrityStorage {
|
||||
private static KEY = 'integrityService';
|
||||
private static readonly KEY = 'integrityService';
|
||||
|
||||
private _storageService: IStorageService;
|
||||
private _value: IStorageData;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 { }
|
||||
}
|
||||
|
||||
111
src/vs/platform/log/node/spdlogService.ts
Normal file
111
src/vs/platform/log/node/spdlogService.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
110
src/vs/platform/state/node/stateService.ts
Normal file
110
src/vs/platform/state/node/stateService.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
56
src/vs/platform/state/test/node/state.test.ts
Normal file
56
src/vs/platform/state/test/node/state.test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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'));
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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')) || {};
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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; }
|
||||
});
|
||||
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
11
src/vs/platform/workbench/common/contextkeys.ts
Normal file
11
src/vs/platform/workbench/common/contextkeys.ts
Normal 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);
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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' }] }));
|
||||
|
||||
Reference in New Issue
Block a user