Merge from vscode 2b0b9136329c181a9e381463a1f7dc3a2d105a34 (#4880)

This commit is contained in:
Karl Burtram
2019-04-05 10:09:18 -07:00
committed by GitHub
parent 9bd7e30d18
commit cb5bcf2248
433 changed files with 8915 additions and 8361 deletions

View File

@@ -4,43 +4,41 @@
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { createHash } from 'crypto';
import * as resources from 'vs/base/common/resources';
import { Event, Emitter } from 'vs/base/common/event';
import * as pfs from 'vs/base/node/pfs';
import * as errors from 'vs/base/common/errors';
import * as collections from 'vs/base/common/collections';
import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { RunOnceScheduler, Delayer } from 'vs/base/common/async';
import { FileChangeType, FileChangesEvent, IContent, IFileService } from 'vs/platform/files/common/files';
import { ConfigurationModel } from 'vs/platform/configuration/common/configurationModels';
import { RunOnceScheduler } from 'vs/base/common/async';
import { FileChangeType, FileChangesEvent, IFileService } from 'vs/platform/files/common/files';
import { ConfigurationModel, ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels';
import { WorkspaceConfigurationModelParser, FolderSettingsModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels';
import { FOLDER_SETTINGS_PATH, TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY } from 'vs/workbench/services/configuration/common/configuration';
import { IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import * as extfs from 'vs/base/node/extfs';
import { FOLDER_SETTINGS_PATH, TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, IConfigurationFileService } from 'vs/workbench/services/configuration/common/configuration';
import { IStoredWorkspaceFolder, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService';
import { WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { extname, join } from 'vs/base/common/path';
import { equals } from 'vs/base/common/objects';
import { Schemas } from 'vs/base/common/network';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IConfigurationModel, compare } from 'vs/platform/configuration/common/configuration';
import { FileServiceBasedUserConfiguration, NodeBasedUserConfiguration } from 'vs/platform/configuration/node/configuration';
import { createSHA1 } from 'vs/base/browser/hash';
export class LocalUserConfiguration extends Disposable {
private readonly userConfigurationResource: URI;
private userConfiguration: NodeBasedUserConfiguration | FileServiceBasedUserConfiguration;
private changeDisposable: IDisposable = Disposable.None;
private readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());
public readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;
constructor(
environmentService: IEnvironmentService
userConfigurationResource: URI,
configurationFileService: IConfigurationFileService
) {
super();
this.userConfiguration = this._register(new NodeBasedUserConfiguration(environmentService.appSettingsPath));
this._register(this.userConfiguration.onDidChangeConfiguration(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)));
this.userConfigurationResource = userConfigurationResource;
this.userConfiguration = this._register(new NodeBasedUserConfiguration(this.userConfigurationResource, configurationFileService));
}
initialize(): Promise<ConfigurationModel> {
@@ -52,6 +50,21 @@ export class LocalUserConfiguration extends Disposable {
}
async adopt(fileService: IFileService): Promise<ConfigurationModel | null> {
if (this.userConfiguration instanceof NodeBasedUserConfiguration) {
const oldConfigurationModel = this.userConfiguration.getConfigurationModel();
this.userConfiguration.dispose();
dispose(this.changeDisposable);
let newConfigurationModel = new ConfigurationModel();
this.userConfiguration = this._register(new FileServiceBasedUserConfiguration(this.userConfigurationResource, fileService));
this.changeDisposable = this._register(this.userConfiguration.onDidChangeConfiguration(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)));
newConfigurationModel = await this.userConfiguration.initialize();
const { added, updated, removed } = compare(oldConfigurationModel, newConfigurationModel);
if (added.length > 0 || updated.length > 0 || removed.length > 0) {
return newConfigurationModel;
}
}
return null;
}
}
@@ -66,10 +79,10 @@ export class RemoteUserConfiguration extends Disposable {
constructor(
remoteAuthority: string,
environmentService: IEnvironmentService
configurationCache: IConfigurationCache
) {
super();
this._userConfiguration = this._cachedConfiguration = new CachedUserConfiguration(remoteAuthority, environmentService);
this._userConfiguration = this._cachedConfiguration = new CachedUserConfiguration(remoteAuthority, configurationCache);
}
initialize(): Promise<ConfigurationModel> {
@@ -108,22 +121,157 @@ export class RemoteUserConfiguration extends Disposable {
}
}
class NodeBasedUserConfiguration extends Disposable {
private configuraitonModel: ConfigurationModel = new ConfigurationModel();
constructor(
private readonly userConfigurationResource: URI,
private readonly configurationFileService: IConfigurationFileService
) {
super();
}
initialize(): Promise<ConfigurationModel> {
return this._load();
}
reload(): Promise<ConfigurationModel> {
return this._load();
}
getConfigurationModel(): ConfigurationModel {
return this.configuraitonModel;
}
async _load(): Promise<ConfigurationModel> {
const exists = await this.configurationFileService.exists(this.userConfigurationResource);
if (exists) {
try {
const content = await this.configurationFileService.resolveContent(this.userConfigurationResource);
const parser = new ConfigurationModelParser(this.userConfigurationResource.toString());
parser.parse(content);
this.configuraitonModel = parser.configurationModel;
} catch (e) {
// ignore error
errors.onUnexpectedError(e);
this.configuraitonModel = new ConfigurationModel();
}
} else {
this.configuraitonModel = new ConfigurationModel();
}
return this.configuraitonModel;
}
}
export class FileServiceBasedUserConfiguration extends Disposable {
private readonly reloadConfigurationScheduler: RunOnceScheduler;
protected readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());
readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;
private fileWatcherDisposable: IDisposable = Disposable.None;
private directoryWatcherDisposable: IDisposable = Disposable.None;
constructor(
private readonly configurationResource: URI,
private readonly fileService: IFileService
) {
super();
this._register(fileService.onFileChanges(e => this.handleFileEvents(e)));
this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50));
this._register(toDisposable(() => {
this.stopWatchingResource();
this.stopWatchingDirectory();
}));
}
private watchResource(): void {
this.fileWatcherDisposable = this.fileService.watch(this.configurationResource);
}
private stopWatchingResource(): void {
this.fileWatcherDisposable.dispose();
this.fileWatcherDisposable = Disposable.None;
}
private watchDirectory(): void {
const directory = resources.dirname(this.configurationResource);
this.directoryWatcherDisposable = this.fileService.watch(directory);
}
private stopWatchingDirectory(): void {
this.directoryWatcherDisposable.dispose();
this.directoryWatcherDisposable = Disposable.None;
}
async initialize(): Promise<ConfigurationModel> {
const exists = await this.fileService.exists(this.configurationResource);
this.onResourceExists(exists);
return this.reload();
}
async reload(): Promise<ConfigurationModel> {
try {
const content = await this.fileService.resolveContent(this.configurationResource);
const parser = new ConfigurationModelParser(this.configurationResource.toString());
parser.parse(content.value);
return parser.configurationModel;
} catch (e) {
return new ConfigurationModel();
}
}
private async handleFileEvents(event: FileChangesEvent): Promise<void> {
const events = event.changes;
let affectedByChanges = false;
// Find changes that affect the resource
for (const event of events) {
affectedByChanges = resources.isEqual(this.configurationResource, event.resource);
if (affectedByChanges) {
if (event.type === FileChangeType.ADDED) {
this.onResourceExists(true);
} else if (event.type === FileChangeType.DELETED) {
this.onResourceExists(false);
}
break;
}
}
if (affectedByChanges) {
this.reloadConfigurationScheduler.schedule();
}
}
private onResourceExists(exists: boolean): void {
if (exists) {
this.stopWatchingDirectory();
this.watchResource();
} else {
this.stopWatchingResource();
this.watchDirectory();
}
}
}
class CachedUserConfiguration extends Disposable {
private readonly _onDidChange: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());
readonly onDidChange: Event<ConfigurationModel> = this._onDidChange.event;
private readonly cachedFolderPath: string;
private readonly cachedConfigurationPath: string;
private readonly key: ConfigurationKey;
private configurationModel: ConfigurationModel;
constructor(
remoteAuthority: string,
private environmentService: IEnvironmentService
private readonly configurationCache: IConfigurationCache
) {
super();
this.cachedFolderPath = join(this.environmentService.userDataPath, 'CachedConfigurations', 'user', remoteAuthority);
this.cachedConfigurationPath = join(this.cachedFolderPath, 'configuration.json');
this.key = { type: 'user', key: remoteAuthority };
this.configurationModel = new ConfigurationModel();
}
@@ -135,44 +283,29 @@ class CachedUserConfiguration extends Disposable {
return this.reload();
}
reload(): Promise<ConfigurationModel> {
return pfs.readFile(this.cachedConfigurationPath)
.then(content => content.toString(), () => '')
.then(content => {
try {
const parsed: IConfigurationModel = JSON.parse(content);
this.configurationModel = new ConfigurationModel(parsed.contents, parsed.keys, parsed.overrides);
} catch (e) {
}
return this.configurationModel;
});
async reload(): Promise<ConfigurationModel> {
const content = await this.configurationCache.read(this.key);
try {
const parsed: IConfigurationModel = JSON.parse(content);
this.configurationModel = new ConfigurationModel(parsed.contents, parsed.keys, parsed.overrides);
} catch (e) {
}
return this.configurationModel;
}
updateConfiguration(configurationModel: ConfigurationModel): Promise<void> {
const raw = JSON.stringify(configurationModel.toJSON());
return this.createCachedFolder().then(created => {
if (created) {
return configurationModel.keys.length ? pfs.writeFile(this.cachedConfigurationPath, raw) : pfs.rimraf(this.cachedFolderPath);
}
return undefined;
});
if (configurationModel.keys.length) {
return this.configurationCache.write(this.key, JSON.stringify(configurationModel.toJSON()));
} else {
return this.configurationCache.remove(this.key);
}
}
private createCachedFolder(): Promise<boolean> {
return Promise.resolve(pfs.exists(this.cachedFolderPath))
.then(undefined, () => false)
.then(exists => exists ? exists : pfs.mkdirp(this.cachedFolderPath).then(() => true, () => false));
}
}
export interface IWorkspaceIdentifier {
id: string;
configPath: URI;
}
export class WorkspaceConfiguration extends Disposable {
private readonly _cachedConfiguration: CachedWorkspaceConfiguration;
private readonly _configurationFileService: IConfigurationFileService;
private _workspaceConfiguration: IWorkspaceConfiguration;
private _workspaceIdentifier: IWorkspaceIdentifier | null = null;
private _fileService: IFileService | null = null;
@@ -181,10 +314,12 @@ export class WorkspaceConfiguration extends Disposable {
public readonly onDidUpdateConfiguration: Event<void> = this._onDidUpdateConfiguration.event;
constructor(
environmentService: IEnvironmentService
configurationCache: IConfigurationCache,
configurationFileService: IConfigurationFileService
) {
super();
this._cachedConfiguration = new CachedWorkspaceConfiguration(environmentService);
this._cachedConfiguration = new CachedWorkspaceConfiguration(configurationCache);
this._configurationFileService = configurationFileService;
this._workspaceConfiguration = this._cachedConfiguration;
}
@@ -251,7 +386,7 @@ export class WorkspaceConfiguration extends Disposable {
if (this._workspaceIdentifier.configPath.scheme === Schemas.file) {
if (!(this._workspaceConfiguration instanceof NodeBasedWorkspaceConfiguration)) {
dispose(this._workspaceConfiguration);
this._workspaceConfiguration = new NodeBasedWorkspaceConfiguration();
this._workspaceConfiguration = new NodeBasedWorkspaceConfiguration(this._configurationFileService);
return true;
}
return false;
@@ -306,14 +441,17 @@ abstract class AbstractWorkspaceConfiguration extends Disposable implements IWor
return this._workspaceIdentifier;
}
load(workspaceIdentifier: IWorkspaceIdentifier): Promise<void> {
async load(workspaceIdentifier: IWorkspaceIdentifier): Promise<void> {
this._workspaceIdentifier = workspaceIdentifier;
return this.loadWorkspaceConfigurationContents(workspaceIdentifier)
.then(contents => {
this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(workspaceIdentifier.id);
this.workspaceConfigurationModelParser.parse(contents);
this.consolidate();
});
this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(workspaceIdentifier.id);
let contents = '';
try {
contents = (await this.loadWorkspaceConfigurationContents(workspaceIdentifier.configPath)) || '';
} catch (e) {
errors.onUnexpectedError(e);
}
this.workspaceConfigurationModelParser.parse(contents);
this.consolidate();
}
getConfigurationModel(): ConfigurationModel {
@@ -338,17 +476,21 @@ abstract class AbstractWorkspaceConfiguration extends Disposable implements IWor
this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel);
}
protected abstract loadWorkspaceConfigurationContents(workspaceIdentifier: IWorkspaceIdentifier): Promise<string>;
protected abstract loadWorkspaceConfigurationContents(workspaceConfigurationResource: URI): Promise<string | undefined>;
}
class NodeBasedWorkspaceConfiguration extends AbstractWorkspaceConfiguration {
protected loadWorkspaceConfigurationContents(workspaceIdentifier: IWorkspaceIdentifier): Promise<string> {
return pfs.readFile(workspaceIdentifier.configPath.fsPath)
.then(contents => contents.toString(), e => {
errors.onUnexpectedError(e);
return '';
});
constructor(private readonly configurationFileService: IConfigurationFileService) {
super();
}
protected async loadWorkspaceConfigurationContents(workspaceConfigurationResource: URI): Promise<string | undefined> {
const exists = await this.configurationFileService.exists(workspaceConfigurationResource);
if (exists) {
return this.configurationFileService.resolveContent(workspaceConfigurationResource);
}
return undefined;
}
}
@@ -356,6 +498,8 @@ class NodeBasedWorkspaceConfiguration extends AbstractWorkspaceConfiguration {
class FileServiceBasedWorkspaceConfiguration extends AbstractWorkspaceConfiguration {
private workspaceConfig: URI | null = null;
private workspaceConfigWatcher: IDisposable;
private readonly reloadConfigurationScheduler: RunOnceScheduler;
constructor(private fileService: IFileService, from?: IWorkspaceConfiguration) {
@@ -363,33 +507,24 @@ class FileServiceBasedWorkspaceConfiguration extends AbstractWorkspaceConfigurat
this.workspaceConfig = from && from.workspaceIdentifier ? from.workspaceIdentifier.configPath : null;
this._register(fileService.onFileChanges(e => this.handleWorkspaceFileEvents(e)));
this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50));
this.watchWorkspaceConfigurationFile();
this._register(toDisposable(() => this.unWatchWorkspaceConfigurtionFile()));
this.workspaceConfigWatcher = this.watchWorkspaceConfigurationFile();
}
private watchWorkspaceConfigurationFile(): void {
private watchWorkspaceConfigurationFile(): IDisposable {
if (this.workspaceConfig) {
this.fileService.watch(this.workspaceConfig);
return this.fileService.watch(this.workspaceConfig);
}
return Disposable.None;
}
private unWatchWorkspaceConfigurtionFile(): void {
if (this.workspaceConfig) {
this.fileService.unwatch(this.workspaceConfig);
protected loadWorkspaceConfigurationContents(workspaceConfigurationResource: URI): Promise<string> {
if (!(this.workspaceConfig && resources.isEqual(this.workspaceConfig, workspaceConfigurationResource))) {
dispose(this.workspaceConfigWatcher);
this.workspaceConfig = workspaceConfigurationResource;
this.workspaceConfigWatcher = this.watchWorkspaceConfigurationFile();
}
}
protected loadWorkspaceConfigurationContents(workspaceIdentifier: IWorkspaceIdentifier): Promise<string> {
if (!(this.workspaceConfig && resources.isEqual(this.workspaceConfig, workspaceIdentifier.configPath))) {
this.unWatchWorkspaceConfigurtionFile();
this.workspaceConfig = workspaceIdentifier.configPath;
this.watchWorkspaceConfigurationFile();
}
return this.fileService.resolveContent(this.workspaceConfig)
.then(content => content.value, e => {
errors.onUnexpectedError(e);
return '';
});
return this.fileService.resolveContent(this.workspaceConfig).then(content => content.value);
}
private handleWorkspaceFileEvents(event: FileChangesEvent): void {
@@ -407,6 +542,12 @@ class FileServiceBasedWorkspaceConfiguration extends AbstractWorkspaceConfigurat
}
}
}
dispose(): void {
super.dispose();
dispose(this.workspaceConfigWatcher);
}
}
class CachedWorkspaceConfiguration extends Disposable implements IWorkspaceConfiguration {
@@ -414,25 +555,24 @@ class CachedWorkspaceConfiguration extends Disposable implements IWorkspaceConfi
private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange: Event<void> = this._onDidChange.event;
private cachedWorkspacePath: string;
private cachedConfigurationPath: string;
workspaceConfigurationModelParser: WorkspaceConfigurationModelParser;
workspaceSettings: ConfigurationModel;
constructor(private environmentService: IEnvironmentService) {
constructor(private readonly configurationCache: IConfigurationCache) {
super();
this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser('');
this.workspaceSettings = new ConfigurationModel();
}
load(workspaceIdentifier: IWorkspaceIdentifier): Promise<void> {
this.createPaths(workspaceIdentifier);
return pfs.readFile(this.cachedConfigurationPath)
.then(contents => {
this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(this.cachedConfigurationPath);
this.workspaceConfigurationModelParser.parse(contents.toString());
this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel);
}, () => { });
async load(workspaceIdentifier: IWorkspaceIdentifier): Promise<void> {
try {
const key = this.getKey(workspaceIdentifier);
const contents = await this.configurationCache.read(key);
this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(key.key);
this.workspaceConfigurationModelParser.parse(contents);
this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel);
} catch (e) {
}
}
get workspaceIdentifier(): IWorkspaceIdentifier | null {
@@ -457,38 +597,24 @@ class CachedWorkspaceConfiguration extends Disposable implements IWorkspaceConfi
async updateWorkspace(workspaceIdentifier: IWorkspaceIdentifier, configurationModel: ConfigurationModel): Promise<void> {
try {
this.createPaths(workspaceIdentifier);
const key = this.getKey(workspaceIdentifier);
if (configurationModel.keys.length) {
const exists = await pfs.exists(this.cachedWorkspacePath);
if (!exists) {
await pfs.mkdirp(this.cachedWorkspacePath);
}
const raw = JSON.stringify(configurationModel.toJSON().contents);
await pfs.writeFile(this.cachedConfigurationPath, raw);
await this.configurationCache.write(key, JSON.stringify(configurationModel.toJSON().contents));
} else {
pfs.rimraf(this.cachedWorkspacePath);
await this.configurationCache.remove(key);
}
} catch (error) {
errors.onUnexpectedError(error);
}
}
private createPaths(workspaceIdentifier: IWorkspaceIdentifier) {
this.cachedWorkspacePath = join(this.environmentService.userDataPath, 'CachedConfigurations', 'workspaces', workspaceIdentifier.id);
this.cachedConfigurationPath = join(this.cachedWorkspacePath, 'workspace.json');
private getKey(workspaceIdentifier: IWorkspaceIdentifier): ConfigurationKey {
return {
type: 'workspaces',
key: workspaceIdentifier.id
};
}
}
function isFolderConfigurationFile(resource: URI): boolean {
const configurationNameResource = URI.from({ scheme: resource.scheme, path: resources.basename(resource) });
return [`${FOLDER_SETTINGS_NAME}.json`, `${TASKS_CONFIGURATION_KEY}.json`, `${LAUNCH_CONFIGURATION_KEY}.json`].some(configurationFileName =>
resources.isEqual(configurationNameResource, URI.from({ scheme: resource.scheme, path: configurationFileName }))); // only workspace config files
}
function isFolderSettingsConfigurationFile(resource: URI): boolean {
return resources.isEqual(URI.from({ scheme: resource.scheme, path: resources.basename(resource) }), URI.from({ scheme: resource.scheme, path: `${FOLDER_SETTINGS_NAME}.json` }));
}
export interface IFolderConfiguration extends IDisposable {
readonly onDidChange: Event<void>;
readonly loaded: boolean;
@@ -503,12 +629,16 @@ export abstract class AbstractFolderConfiguration extends Disposable implements
private _cache: ConfigurationModel;
private _loaded: boolean = false;
private readonly configurationNames: string[];
protected readonly configurationResources: URI[];
protected readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange: Event<void> = this._onDidChange.event;
constructor(protected readonly folder: URI, workbenchState: WorkbenchState, from?: AbstractFolderConfiguration) {
constructor(protected readonly configurationFolder: URI, workbenchState: WorkbenchState, from?: AbstractFolderConfiguration) {
super();
this.configurationNames = [FOLDER_SETTINGS_NAME /*First one should be settings */, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY];
this.configurationResources = this.configurationNames.map(name => resources.joinPath(this.configurationFolder, `${name}.json`));
this._folderSettingsModelParser = from ? from._folderSettingsModelParser : new FolderSettingsModelParser(FOLDER_SETTINGS_PATH, WorkbenchState.WORKSPACE === workbenchState ? [ConfigurationScope.RESOURCE] : [ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE]);
this._standAloneConfigurations = from ? from._standAloneConfigurations : [];
this._cache = from ? from._cache : new ConfigurationModel();
@@ -518,23 +648,37 @@ export abstract class AbstractFolderConfiguration extends Disposable implements
return this._loaded;
}
loadConfiguration(): Promise<ConfigurationModel> {
return this.loadFolderConfigurationContents()
.then((contents) => {
async loadConfiguration(): Promise<ConfigurationModel> {
const configurationContents = await Promise.all(this.configurationResources.map(resource =>
this.loadConfigurationResourceContents(resource)
.then(undefined, error => {
/* never fail */
errors.onUnexpectedError(error);
return undefined;
})));
// reset
this._standAloneConfigurations = [];
this._folderSettingsModelParser.parse('');
// reset
this._standAloneConfigurations = [];
this._folderSettingsModelParser.parse('');
// parse
this.parseContents(contents);
// parse
if (configurationContents[0]) {
this._folderSettingsModelParser.parse(configurationContents[0]);
}
for (let index = 1; index < configurationContents.length; index++) {
const contents = configurationContents[index];
if (contents) {
const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(this.configurationResources[index].toString(), this.configurationNames[index]);
standAloneConfigurationModelParser.parse(contents);
this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel);
}
}
// Consolidate (support *.json files in the workspace settings folder)
this.consolidate();
// Consolidate (support *.json files in the workspace settings folder)
this.consolidate();
this._loaded = true;
return this._cache;
});
this._loaded = true;
return this._cache;
}
reprocess(): ConfigurationModel {
@@ -550,109 +694,41 @@ export abstract class AbstractFolderConfiguration extends Disposable implements
this._cache = this._folderSettingsModelParser.configurationModel.merge(...this._standAloneConfigurations);
}
private parseContents(contents: { resource: URI, value: string }[]): void {
for (const content of contents) {
if (isFolderSettingsConfigurationFile(content.resource)) {
this._folderSettingsModelParser.parse(content.value);
} else {
const name = resources.basename(content.resource);
const matches = /([^\.]*)*\.json/.exec(name);
if (matches && matches[1]) {
const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(content.resource.toString(), matches[1]);
standAloneConfigurationModelParser.parse(content.value);
this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel);
}
}
}
}
protected abstract loadFolderConfigurationContents(): Promise<{ resource: URI, value: string }[]>;
protected abstract loadConfigurationResourceContents(configurationResource: URI): Promise<string | undefined>;
}
export class NodeBasedFolderConfiguration extends AbstractFolderConfiguration {
private readonly folderConfigurationPath: URI;
constructor(folder: URI, configFolderRelativePath: string, workbenchState: WorkbenchState) {
super(folder, workbenchState);
this.folderConfigurationPath = resources.joinPath(folder, configFolderRelativePath);
constructor(private readonly configurationFileService: IConfigurationFileService, configurationFolder: URI, workbenchState: WorkbenchState) {
super(configurationFolder, workbenchState);
}
protected loadFolderConfigurationContents(): Promise<{ resource: URI, value: string }[]> {
return this.resolveStat(this.folderConfigurationPath).then(stat => {
if (!stat.isDirectory || !stat.children) {
return Promise.resolve([]);
}
return this.resolveContents(stat.children.filter(stat => isFolderConfigurationFile(stat.resource))
.map(stat => stat.resource));
}, err => [] /* never fail this call */)
.then(undefined, e => {
errors.onUnexpectedError(e);
return [];
});
}
private resolveContents(resources: URI[]): Promise<{ resource: URI, value: string }[]> {
return Promise.all(resources.map(resource =>
pfs.readFile(resource.fsPath)
.then(contents => ({ resource, value: contents.toString() }))));
}
private resolveStat(resource: URI): Promise<{ resource: URI, isDirectory?: boolean, children?: { resource: URI; }[] }> {
return new Promise<{ resource: URI, isDirectory?: boolean, children?: { resource: URI; }[] }>((c, e) => {
extfs.readdir(resource.fsPath, (error, children) => {
if (error) {
if ((<any>error).code === 'ENOTDIR') {
c({ resource });
} else {
e(error);
}
} else {
c({
resource,
isDirectory: true,
children: children.map(child => { return { resource: resources.joinPath(resource, child) }; })
});
}
});
});
protected async loadConfigurationResourceContents(configurationResource: URI): Promise<string | undefined> {
const exists = await this.configurationFileService.exists(configurationResource);
if (exists) {
return this.configurationFileService.resolveContent(configurationResource);
}
return undefined;
}
}
export class FileServiceBasedFolderConfiguration extends AbstractFolderConfiguration {
private reloadConfigurationScheduler: RunOnceScheduler;
private readonly folderConfigurationPath: URI;
private readonly loadConfigurationDelayer = new Delayer<Array<{ resource: URI, value: string }>>(50);
private changeEventTriggerScheduler: RunOnceScheduler;
constructor(folder: URI, private configFolderRelativePath: string, workbenchState: WorkbenchState, private fileService: IFileService, from?: AbstractFolderConfiguration) {
super(folder, workbenchState, from);
this.folderConfigurationPath = resources.joinPath(folder, configFolderRelativePath);
this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50));
constructor(configurationFolder: URI, workbenchState: WorkbenchState, private fileService: IFileService, from?: AbstractFolderConfiguration) {
super(configurationFolder, workbenchState, from);
this.changeEventTriggerScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50));
this._register(fileService.onFileChanges(e => this.handleWorkspaceFileEvents(e)));
}
protected loadFolderConfigurationContents(): Promise<Array<{ resource: URI, value: string }>> {
return Promise.resolve(this.loadConfigurationDelayer.trigger(() => this.doLoadFolderConfigurationContents()));
}
private doLoadFolderConfigurationContents(): Promise<Array<{ resource: URI, value: string }>> {
const workspaceFilePathToConfiguration: { [relativeWorkspacePath: string]: Promise<IContent | undefined> } = Object.create(null);
const bulkContentFetchromise = Promise.resolve(this.fileService.resolve(this.folderConfigurationPath))
.then(stat => {
if (stat.isDirectory && stat.children) {
stat.children
.filter(child => isFolderConfigurationFile(child.resource))
.forEach(child => {
const folderRelativePath = this.toFolderRelativePath(child.resource);
if (folderRelativePath) {
workspaceFilePathToConfiguration[folderRelativePath] = Promise.resolve(this.fileService.resolveContent(child.resource)).then(undefined, errors.onUnexpectedError);
}
});
}
}).then(undefined, err => [] /* never fail this call */);
return bulkContentFetchromise.then(() => Promise.all<IContent>(collections.values(workspaceFilePathToConfiguration))).then(contents => contents.filter(content => content !== undefined));
protected async loadConfigurationResourceContents(configurationResource: URI): Promise<string | undefined> {
const exists = await this.fileService.exists(configurationResource);
if (exists) {
const contents = await this.fileService.resolveContent(configurationResource);
return contents.value;
}
return undefined;
}
private handleWorkspaceFileEvents(event: FileChangesEvent): void {
@@ -664,9 +740,9 @@ export class FileServiceBasedFolderConfiguration extends AbstractFolderConfigura
const resource = events[i].resource;
const basename = resources.basename(resource);
const isJson = extname(basename) === '.json';
const isDeletedSettingsFolder = (events[i].type === FileChangeType.DELETED && basename === this.configFolderRelativePath);
const isConfigurationFolderDeleted = (events[i].type === FileChangeType.DELETED && resources.isEqual(resource, this.configurationFolder));
if (!isJson && !isDeletedSettingsFolder) {
if (!isJson && !isConfigurationFolderDeleted) {
continue; // only JSON files or the actual settings folder
}
@@ -676,72 +752,69 @@ export class FileServiceBasedFolderConfiguration extends AbstractFolderConfigura
}
// Handle case where ".vscode" got deleted
if (isDeletedSettingsFolder) {
if (isConfigurationFolderDeleted) {
affectedByChanges = true;
break;
}
// only valid workspace config files
if (!isFolderConfigurationFile(resource)) {
continue;
if (this.configurationResources.some(configurationResource => resources.isEqual(configurationResource, resource))) {
affectedByChanges = true;
break;
}
affectedByChanges = true;
break;
}
if (affectedByChanges) {
this.reloadConfigurationScheduler.schedule();
this.changeEventTriggerScheduler.schedule();
}
}
private toFolderRelativePath(resource: URI): string | undefined {
if (resources.isEqualOrParent(resource, this.folderConfigurationPath)) {
return resources.relativePath(this.folderConfigurationPath, resource);
if (resources.isEqualOrParent(resource, this.configurationFolder)) {
return resources.relativePath(this.configurationFolder, resource);
}
return undefined;
}
}
export class CachedFolderConfiguration extends Disposable implements IFolderConfiguration {
class CachedFolderConfiguration extends Disposable implements IFolderConfiguration {
private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange: Event<void> = this._onDidChange.event;
private readonly cachedFolderPath: string;
private readonly cachedConfigurationPath: string;
private configurationModel: ConfigurationModel;
private readonly key: Thenable<ConfigurationKey>;
loaded: boolean = false;
constructor(
folder: URI,
configFolderRelativePath: string,
environmentService: IEnvironmentService) {
private readonly configurationCache: IConfigurationCache
) {
super();
this.cachedFolderPath = join(environmentService.userDataPath, 'CachedConfigurations', 'folders', createHash('md5').update(join(folder.path, configFolderRelativePath)).digest('hex'));
this.cachedConfigurationPath = join(this.cachedFolderPath, 'configuration.json');
this.key = createSHA1(join(folder.path, configFolderRelativePath)).then(key => (<ConfigurationKey>{ type: 'folder', key }));
this.configurationModel = new ConfigurationModel();
}
loadConfiguration(): Promise<ConfigurationModel> {
return pfs.readFile(this.cachedConfigurationPath)
.then(contents => {
const parsed: IConfigurationModel = JSON.parse(contents.toString());
this.configurationModel = new ConfigurationModel(parsed.contents, parsed.keys, parsed.overrides);
this.loaded = true;
return this.configurationModel;
}, () => this.configurationModel);
async loadConfiguration(): Promise<ConfigurationModel> {
try {
const key = await this.key;
const contents = await this.configurationCache.read(key);
const parsed: IConfigurationModel = JSON.parse(contents.toString());
this.configurationModel = new ConfigurationModel(parsed.contents, parsed.keys, parsed.overrides);
this.loaded = true;
} catch (e) {
}
return this.configurationModel;
}
updateConfiguration(configurationModel: ConfigurationModel): Promise<void> {
const raw = JSON.stringify(configurationModel.toJSON());
return this.createCachedFolder().then(created => {
if (created) {
return configurationModel.keys.length ? pfs.writeFile(this.cachedConfigurationPath, raw) : pfs.rimraf(this.cachedFolderPath);
}
return undefined;
});
async updateConfiguration(configurationModel: ConfigurationModel): Promise<void> {
const key = await this.key;
if (configurationModel.keys.length) {
await this.configurationCache.write(key, JSON.stringify(configurationModel.toJSON()));
} else {
await this.configurationCache.remove(key);
}
}
reprocess(): ConfigurationModel {
@@ -751,12 +824,6 @@ export class CachedFolderConfiguration extends Disposable implements IFolderConf
getUnsupportedKeys(): string[] {
return [];
}
private createCachedFolder(): Promise<boolean> {
return Promise.resolve(pfs.exists(this.cachedFolderPath))
.then(undefined, () => false)
.then(exists => exists ? exists : pfs.mkdirp(this.cachedFolderPath).then(() => true, () => false));
}
}
export class FolderConfiguration extends Disposable implements IFolderConfiguration {
@@ -765,24 +832,27 @@ export class FolderConfiguration extends Disposable implements IFolderConfigurat
readonly onDidChange: Event<void> = this._onDidChange.event;
private folderConfiguration: IFolderConfiguration;
private readonly configurationFolder: URI;
private cachedFolderConfiguration: CachedFolderConfiguration;
private _loaded: boolean = false;
constructor(
readonly workspaceFolder: IWorkspaceFolder,
private readonly configFolderRelativePath: string,
configFolderRelativePath: string,
private readonly workbenchState: WorkbenchState,
private environmentService: IEnvironmentService,
configurationFileService: IConfigurationFileService,
configurationCache: IConfigurationCache,
fileService?: IFileService
) {
super();
this.cachedFolderConfiguration = new CachedFolderConfiguration(this.workspaceFolder.uri, this.configFolderRelativePath, this.environmentService);
this.configurationFolder = resources.joinPath(workspaceFolder.uri, configFolderRelativePath);
this.cachedFolderConfiguration = new CachedFolderConfiguration(workspaceFolder.uri, configFolderRelativePath, configurationCache);
this.folderConfiguration = this.cachedFolderConfiguration;
if (fileService) {
this.folderConfiguration = new FileServiceBasedFolderConfiguration(this.workspaceFolder.uri, this.configFolderRelativePath, this.workbenchState, fileService);
} else if (this.workspaceFolder.uri.scheme === Schemas.file) {
this.folderConfiguration = new NodeBasedFolderConfiguration(this.workspaceFolder.uri, this.configFolderRelativePath, this.workbenchState);
this.folderConfiguration = new FileServiceBasedFolderConfiguration(this.configurationFolder, this.workbenchState, fileService);
} else if (workspaceFolder.uri.scheme === Schemas.file) {
this.folderConfiguration = new NodeBasedFolderConfiguration(configurationFileService, this.configurationFolder, this.workbenchState);
}
this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange()));
}
@@ -817,7 +887,7 @@ export class FolderConfiguration extends Disposable implements IFolderConfigurat
}
private adoptFromCachedConfiguration(fileService: IFileService): Promise<boolean> {
const folderConfiguration = new FileServiceBasedFolderConfiguration(this.workspaceFolder.uri, this.configFolderRelativePath, this.workbenchState, fileService);
const folderConfiguration = new FileServiceBasedFolderConfiguration(this.configurationFolder, this.workbenchState, fileService);
return folderConfiguration.loadConfiguration()
.then(() => {
this.folderConfiguration = folderConfiguration;
@@ -829,7 +899,7 @@ export class FolderConfiguration extends Disposable implements IFolderConfigurat
private adoptFromNodeBasedConfiguration(fileService: IFileService): Promise<boolean> {
const oldFolderConfiguration = this.folderConfiguration;
this.folderConfiguration = new FileServiceBasedFolderConfiguration(this.workspaceFolder.uri, this.configFolderRelativePath, this.workbenchState, fileService, <AbstractFolderConfiguration>oldFolderConfiguration);
this.folderConfiguration = new FileServiceBasedFolderConfiguration(this.configurationFolder, this.workbenchState, fileService, <AbstractFolderConfiguration>oldFolderConfiguration);
oldFolderConfiguration.dispose();
this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange()));
return Promise.resolve(false);
@@ -841,7 +911,7 @@ export class FolderConfiguration extends Disposable implements IFolderConfigurat
}
private updateCache(): Promise<void> {
if (this.workspaceFolder.uri.scheme !== Schemas.file && this.folderConfiguration instanceof FileServiceBasedFolderConfiguration) {
if (this.configurationFolder.scheme !== Schemas.file && this.folderConfiguration instanceof FileServiceBasedFolderConfiguration) {
return this.folderConfiguration.loadConfiguration()
.then(configurationModel => this.cachedFolderConfiguration.updateConfiguration(configurationModel));
}

View File

@@ -10,25 +10,20 @@ import { ResourceMap } from 'vs/base/common/map';
import { equals, deepClone } from 'vs/base/common/objects';
import { Disposable } from 'vs/base/common/lifecycle';
import { Queue, Barrier } from 'vs/base/common/async';
import { writeFile } from 'vs/base/node/pfs';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { IWorkspaceContextService, Workspace, WorkbenchState, IWorkspaceFolder, toWorkspaceFolders, IWorkspaceFoldersChangeEvent, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { isLinux } from 'vs/base/common/platform';
import { IFileService } from 'vs/platform/files/common/files';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ConfigurationChangeEvent, ConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels';
import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Configuration, WorkspaceConfigurationChangeEvent, AllKeysConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels';
import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration';
import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, IConfigurationFileService } from 'vs/workbench/services/configuration/common/configuration';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationNode, IConfigurationRegistry, Extensions, IConfigurationPropertySchema, allSettings, windowSettings, resourceSettings, applicationSettings } from 'vs/platform/configuration/common/configurationRegistry';
import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings } from 'vs/platform/configuration/common/configurationRegistry';
import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, isSingleFolderWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, useSlashForPath, getStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import product from 'vs/platform/product/node/product';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ConfigurationEditingService } from 'vs/workbench/services/configuration/common/configurationEditingService';
import { WorkspaceConfiguration, FolderConfiguration, RemoteUserConfiguration, LocalUserConfiguration } from 'vs/workbench/services/configuration/node/configuration';
import { WorkspaceConfiguration, FolderConfiguration, RemoteUserConfiguration, LocalUserConfiguration } from 'vs/workbench/services/configuration/browser/configuration';
import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService';
import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
import { localize } from 'vs/nls';
@@ -36,7 +31,6 @@ import { isEqual, dirname } from 'vs/base/common/resources';
import { mark } from 'vs/base/common/performance';
import { Schemas } from 'vs/base/common/network';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
export class WorkspaceService extends Disposable implements IConfigurationService, IWorkspaceContextService {
@@ -44,9 +38,10 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
private workspace: Workspace;
private completeWorkspaceBarrier: Barrier;
private readonly configurationCache: IConfigurationCache;
private _configuration: Configuration;
private defaultConfiguration: DefaultConfigurationModel;
private localUserConfiguration: LocalUserConfiguration;
private localUserConfiguration: LocalUserConfiguration | null = null;
private remoteUserConfiguration: RemoteUserConfiguration | null = null;
private workspaceConfiguration: WorkspaceConfiguration;
private cachedFolderConfigs: ResourceMap<FolderConfiguration>;
@@ -69,18 +64,25 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
private configurationEditingService: ConfigurationEditingService;
private jsonEditingService: JSONEditingService;
constructor(configuration: IWindowConfiguration, private environmentService: IEnvironmentService, private remoteAgentService: IRemoteAgentService, private workspaceSettingsRootFolder: string = FOLDER_CONFIG_FOLDER_NAME) {
constructor(
{ userSettingsResource, remoteAuthority, configurationCache }: { userSettingsResource?: URI, remoteAuthority?: string, configurationCache: IConfigurationCache },
private readonly configurationFileService: IConfigurationFileService,
private readonly remoteAgentService: IRemoteAgentService,
) {
super();
this.completeWorkspaceBarrier = new Barrier();
this.defaultConfiguration = new DefaultConfigurationModel();
this.localUserConfiguration = this._register(new LocalUserConfiguration(environmentService));
this._register(this.localUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onLocalUserConfigurationChanged(userConfiguration)));
if (configuration.remoteAuthority) {
this.remoteUserConfiguration = this._register(new RemoteUserConfiguration(configuration.remoteAuthority, environmentService));
this.configurationCache = configurationCache;
if (userSettingsResource) {
this.localUserConfiguration = this._register(new LocalUserConfiguration(userSettingsResource, configurationFileService));
this._register(this.localUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onLocalUserConfigurationChanged(userConfiguration)));
}
if (remoteAuthority) {
this.remoteUserConfiguration = this._register(new RemoteUserConfiguration(remoteAuthority, configurationCache));
this._register(this.remoteUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onRemoteUserConfigurationChanged(userConfiguration)));
}
this.workspaceConfiguration = this._register(new WorkspaceConfiguration(environmentService));
this.workspaceConfiguration = this._register(new WorkspaceConfiguration(configurationCache, this.configurationFileService));
this._register(this.workspaceConfiguration.onDidUpdateConfiguration(() => this.onWorkspaceConfigurationChanged()));
this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidSchemaChange(e => this.registerConfigurationSchemas()));
@@ -284,10 +286,10 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
return this._configuration.keys();
}
initialize(arg: IWorkspaceInitializationPayload, postInitialisationTask: () => void = () => null): Promise<any> {
initialize(arg: IWorkspaceInitializationPayload): Promise<any> {
mark('willInitWorkspaceService');
return this.createWorkspace(arg)
.then(workspace => this.updateWorkspaceAndInitializeConfiguration(workspace, postInitialisationTask)).then(() => {
.then(workspace => this.updateWorkspaceAndInitializeConfiguration(workspace)).then(() => {
mark('didInitWorkspaceService');
});
}
@@ -295,7 +297,14 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
acquireFileService(fileService: IFileService): void {
this.fileService = fileService;
const changedWorkspaceFolders: IWorkspaceFolder[] = [];
this.localUserConfiguration.adopt(fileService);
if (this.localUserConfiguration) {
this.localUserConfiguration.adopt(fileService)
.then(changedModel => {
if (changedModel) {
this.onLocalUserConfigurationChanged(changedModel);
}
});
}
Promise.all([this.workspaceConfiguration.adopt(fileService), ...this.cachedFolderConfigs.values()
.map(folderConfiguration => folderConfiguration.adopt(fileService)
.then(result => {
@@ -382,7 +391,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
}
}
private updateWorkspaceAndInitializeConfiguration(workspace: Workspace, postInitialisationTask: () => void): Promise<void> {
private updateWorkspaceAndInitializeConfiguration(workspace: Workspace): Promise<void> {
const hasWorkspaceBefore = !!this.workspace;
let previousState: WorkbenchState;
let previousWorkspacePath: string | undefined;
@@ -399,8 +408,6 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
return this.initializeConfiguration().then(() => {
postInitialisationTask(); // Post initialisation task should be run before triggering events.
// Trigger changes after configuration initialization so that configuration is up to date.
if (hasWorkspaceBefore) {
const newState = this.getWorkbenchState();
@@ -446,12 +453,12 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
}
private initializeUserConfiguration(): Promise<{ local: ConfigurationModel, remote: ConfigurationModel }> {
return Promise.all([this.localUserConfiguration.initialize(), this.remoteUserConfiguration ? this.remoteUserConfiguration.initialize() : Promise.resolve(new ConfigurationModel())])
return Promise.all([this.localUserConfiguration ? this.localUserConfiguration.initialize() : Promise.resolve(new ConfigurationModel()), this.remoteUserConfiguration ? this.remoteUserConfiguration.initialize() : Promise.resolve(new ConfigurationModel())])
.then(([local, remote]) => ({ local, remote }));
}
private reloadUserConfiguration(key?: string): Promise<{ local: ConfigurationModel, remote: ConfigurationModel }> {
return Promise.all([this.localUserConfiguration.reload(), this.remoteUserConfiguration ? this.remoteUserConfiguration.reload() : Promise.resolve(new ConfigurationModel())])
return Promise.all([this.localUserConfiguration ? this.localUserConfiguration.reload() : Promise.resolve(new ConfigurationModel()), this.remoteUserConfiguration ? this.remoteUserConfiguration.reload() : Promise.resolve(new ConfigurationModel())])
.then(([local, remote]) => ({ local, remote }));
}
@@ -622,7 +629,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
return Promise.all([...folders.map(folder => {
let folderConfiguration = this.cachedFolderConfigs.get(folder.uri);
if (!folderConfiguration) {
folderConfiguration = new FolderConfiguration(folder, this.workspaceSettingsRootFolder, this.getWorkbenchState(), this.environmentService, this.fileService);
folderConfiguration = new FolderConfiguration(folder, FOLDER_CONFIG_FOLDER_NAME, this.getWorkbenchState(), this.configurationFileService, this.configurationCache, this.fileService);
this._register(folderConfiguration.onDidChange(() => this.onWorkspaceFolderConfigurationChanged(folder)));
this.cachedFolderConfigs.set(folder.uri, this._register(folderConfiguration));
}
@@ -709,99 +716,4 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
}
return {};
}
}
interface IExportedConfigurationNode {
name: string;
description: string;
default: any;
type?: string | string[];
enum?: any[];
enumDescriptions?: string[];
}
interface IConfigurationExport {
settings: IExportedConfigurationNode[];
buildTime: number;
commit?: string;
buildNumber?: number;
}
export class DefaultConfigurationExportHelper {
constructor(
@IEnvironmentService environmentService: IEnvironmentService,
@IExtensionService private readonly extensionService: IExtensionService,
@ICommandService private readonly commandService: ICommandService) {
if (environmentService.args['export-default-configuration']) {
this.writeConfigModelAndQuit(environmentService.args['export-default-configuration']);
}
}
private writeConfigModelAndQuit(targetPath: string): Promise<void> {
return Promise.resolve(this.extensionService.whenInstalledExtensionsRegistered())
.then(() => this.writeConfigModel(targetPath))
.then(() => this.commandService.executeCommand('workbench.action.quit'))
.then(() => { });
}
private writeConfigModel(targetPath: string): Promise<void> {
const config = this.getConfigModel();
const resultString = JSON.stringify(config, undefined, ' ');
return writeFile(targetPath, resultString);
}
private getConfigModel(): IConfigurationExport {
const configRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
const configurations = configRegistry.getConfigurations().slice();
const settings: IExportedConfigurationNode[] = [];
const processProperty = (name: string, prop: IConfigurationPropertySchema) => {
const propDetails: IExportedConfigurationNode = {
name,
description: prop.description || prop.markdownDescription || '',
default: prop.default,
type: prop.type
};
if (prop.enum) {
propDetails.enum = prop.enum;
}
if (prop.enumDescriptions || prop.markdownEnumDescriptions) {
propDetails.enumDescriptions = prop.enumDescriptions || prop.markdownEnumDescriptions;
}
settings.push(propDetails);
};
const processConfig = (config: IConfigurationNode) => {
if (config.properties) {
for (let name in config.properties) {
processProperty(name, config.properties[name]);
}
}
if (config.allOf) {
config.allOf.forEach(processConfig);
}
};
configurations.forEach(processConfig);
const excludedProps = configRegistry.getExcludedConfigurationProperties();
for (let name in excludedProps) {
processProperty(name, excludedProps[name]);
}
const result: IConfigurationExport = {
settings: settings.sort((a, b) => a.name.localeCompare(b.name)),
buildTime: Date.now(),
commit: product.commit,
buildNumber: product.settingsSearchBuildId
};
return result;
}
}
}

View File

@@ -3,6 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
export const FOLDER_CONFIG_FOLDER_NAME = '.azuredatastudio';
export const FOLDER_SETTINGS_NAME = 'settings';
export const FOLDER_SETTINGS_PATH = `${FOLDER_CONFIG_FOLDER_NAME}/${FOLDER_SETTINGS_NAME}.json`;
@@ -19,3 +21,19 @@ export const LAUNCH_CONFIGURATION_KEY = 'launch';
export const WORKSPACE_STANDALONE_CONFIGURATIONS = Object.create(null);
WORKSPACE_STANDALONE_CONFIGURATIONS[TASKS_CONFIGURATION_KEY] = `${FOLDER_CONFIG_FOLDER_NAME}/${TASKS_CONFIGURATION_KEY}.json`;
WORKSPACE_STANDALONE_CONFIGURATIONS[LAUNCH_CONFIGURATION_KEY] = `${FOLDER_CONFIG_FOLDER_NAME}/${LAUNCH_CONFIGURATION_KEY}.json`;
export type ConfigurationKey = { type: 'user' | 'workspaces' | 'folder', key: string };
export interface IConfigurationCache {
read(key: ConfigurationKey): Promise<string>;
write(key: ConfigurationKey, content: string): Promise<void>;
remove(key: ConfigurationKey): Promise<void>;
}
export interface IConfigurationFileService {
exists(resource: URI): Promise<boolean>;
resolveContent(resource: URI): Promise<string>;
}

View File

@@ -0,0 +1,82 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as pfs from 'vs/base/node/pfs';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { join } from 'vs/base/common/path';
import { IConfigurationCache, ConfigurationKey } from 'vs/workbench/services/configuration/common/configuration';
export class ConfigurationCache implements IConfigurationCache {
private readonly cachedConfigurations: Map<string, CachedConfiguration> = new Map<string, CachedConfiguration>();
constructor(private readonly environmentService: IEnvironmentService) {
}
read(key: ConfigurationKey): Promise<string> {
return this.getCachedConfiguration(key).read();
}
write(key: ConfigurationKey, content: string): Promise<void> {
return this.getCachedConfiguration(key).save(content);
}
remove(key: ConfigurationKey): Promise<void> {
return this.getCachedConfiguration(key).remove();
}
private getCachedConfiguration({ type, key }: ConfigurationKey): CachedConfiguration {
const k = `${type}:${key}`;
let cachedConfiguration = this.cachedConfigurations.get(k);
if (!cachedConfiguration) {
cachedConfiguration = new CachedConfiguration({ type, key }, this.environmentService);
this.cachedConfigurations.set(k, cachedConfiguration);
}
return cachedConfiguration;
}
}
class CachedConfiguration {
private cachedConfigurationFolderPath: string;
private cachedConfigurationFilePath: string;
constructor(
{ type, key }: ConfigurationKey,
environmentService: IEnvironmentService
) {
this.cachedConfigurationFolderPath = join(environmentService.userDataPath, 'CachedConfigurations', type, key);
this.cachedConfigurationFilePath = join(this.cachedConfigurationFolderPath, type === 'workspaces' ? 'workspace.json' : 'configuration.json');
}
async read(): Promise<string> {
try {
const content = await pfs.readFile(this.cachedConfigurationFilePath);
return content.toString();
} catch (e) {
return '';
}
}
async save(content: string): Promise<void> {
const created = await this.createCachedFolder();
if (created) {
await pfs.writeFile(this.cachedConfigurationFilePath, content);
}
}
remove(): Promise<void> {
return pfs.rimraf(this.cachedConfigurationFolderPath);
}
private createCachedFolder(): Promise<boolean> {
return Promise.resolve(pfs.exists(this.cachedConfigurationFolderPath))
.then(undefined, () => false)
.then(exists => exists ? exists : pfs.mkdirp(this.cachedConfigurationFolderPath).then(() => true, () => false));
}
}

View File

@@ -0,0 +1,107 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { writeFile } from 'vs/base/node/pfs';
import product from 'vs/platform/product/node/product';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationNode, IConfigurationRegistry, Extensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ICommandService } from 'vs/platform/commands/common/commands';
interface IExportedConfigurationNode {
name: string;
description: string;
default: any;
type?: string | string[];
enum?: any[];
enumDescriptions?: string[];
}
interface IConfigurationExport {
settings: IExportedConfigurationNode[];
buildTime: number;
commit?: string;
buildNumber?: number;
}
export class DefaultConfigurationExportHelper {
constructor(
@IEnvironmentService environmentService: IEnvironmentService,
@IExtensionService private readonly extensionService: IExtensionService,
@ICommandService private readonly commandService: ICommandService) {
if (environmentService.args['export-default-configuration']) {
this.writeConfigModelAndQuit(environmentService.args['export-default-configuration']);
}
}
private writeConfigModelAndQuit(targetPath: string): Promise<void> {
return Promise.resolve(this.extensionService.whenInstalledExtensionsRegistered())
.then(() => this.writeConfigModel(targetPath))
.then(() => this.commandService.executeCommand('workbench.action.quit'))
.then(() => { });
}
private writeConfigModel(targetPath: string): Promise<void> {
const config = this.getConfigModel();
const resultString = JSON.stringify(config, undefined, ' ');
return writeFile(targetPath, resultString);
}
private getConfigModel(): IConfigurationExport {
const configRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
const configurations = configRegistry.getConfigurations().slice();
const settings: IExportedConfigurationNode[] = [];
const processProperty = (name: string, prop: IConfigurationPropertySchema) => {
const propDetails: IExportedConfigurationNode = {
name,
description: prop.description || prop.markdownDescription || '',
default: prop.default,
type: prop.type
};
if (prop.enum) {
propDetails.enum = prop.enum;
}
if (prop.enumDescriptions || prop.markdownEnumDescriptions) {
propDetails.enumDescriptions = prop.enumDescriptions || prop.markdownEnumDescriptions;
}
settings.push(propDetails);
};
const processConfig = (config: IConfigurationNode) => {
if (config.properties) {
for (let name in config.properties) {
processProperty(name, config.properties[name]);
}
}
if (config.allOf) {
config.allOf.forEach(processConfig);
}
};
configurations.forEach(processConfig);
const excludedProps = configRegistry.getExcludedConfigurationProperties();
for (let name in excludedProps) {
processProperty(name, excludedProps[name]);
}
const result: IConfigurationExport = {
settings: settings.sort((a, b) => a.name.localeCompare(b.name)),
buildTime: Date.now(),
commit: product.commit,
buildNumber: product.settingsSearchBuildId
};
return result;
}
}

View File

@@ -0,0 +1,21 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as pfs from 'vs/base/node/pfs';
import { IConfigurationFileService } from 'vs/workbench/services/configuration/common/configuration';
import { URI } from 'vs/base/common/uri';
export class ConfigurationFileService implements IConfigurationFileService {
exists(resource: URI): Promise<boolean> {
return pfs.exists(resource.fsPath);
}
async resolveContent(resource: URI): Promise<string> {
const contents = await pfs.readFile(resource.fsPath);
return contents.toString();
}
}

View File

@@ -14,31 +14,33 @@ import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/
import { parseArgs } from 'vs/platform/environment/node/argv';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import * as extfs from 'vs/base/node/extfs';
import { TestTextFileService, TestTextResourceConfigurationService, workbenchInstantiationService, TestLifecycleService, TestEnvironmentService, TestStorageService } from 'vs/workbench/test/workbenchTestServices';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
import { TestTextFileService, TestTextResourceConfigurationService, workbenchInstantiationService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices';
import * as uuid from 'vs/base/common/uuid';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService';
import { FileService } from 'vs/workbench/services/files/node/fileService';
import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
import { LegacyFileService } from 'vs/workbench/services/files/node/fileService';
import { ConfigurationEditingService, ConfigurationEditingError, ConfigurationEditingErrorCode } from 'vs/workbench/services/configuration/common/configurationEditingService';
import { IFileService } from 'vs/platform/files/common/files';
import { WORKSPACE_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { mkdirp } from 'vs/base/node/pfs';
import { mkdirp, rimraf, RimRafMode } from 'vs/base/node/pfs';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { CommandService } from 'vs/workbench/services/commands/common/commandService';
import { URI } from 'vs/base/common/uri';
import { createHash } from 'crypto';
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { FileService2 } from 'vs/workbench/services/files2/common/fileService2';
import { NullLogService } from 'vs/platform/log/common/log';
import { Schemas } from 'vs/base/common/network';
import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider';
import { IFileService } from 'vs/platform/files/common/files';
import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache';
import { ConfigurationFileService } from 'vs/workbench/services/configuration/node/configurationFileService';
class SettingsTestEnvironmentService extends EnvironmentService {
@@ -85,7 +87,7 @@ suite('ConfigurationEditingService', () => {
.then(() => setUpServices());
});
async function setUpWorkspace(): Promise<boolean> {
async function setUpWorkspace(): Promise<void> {
const id = uuid.generateUuid();
parentDir = path.join(os.tmpdir(), 'vsctests', id);
workspaceDir = path.join(parentDir, 'workspaceconfig', id);
@@ -105,11 +107,19 @@ suite('ConfigurationEditingService', () => {
instantiationService.stub(IEnvironmentService, environmentService);
const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {});
instantiationService.stub(IRemoteAgentService, remoteAgentService);
const workspaceService = new WorkspaceService(<IWindowConfiguration>{}, environmentService, remoteAgentService);
const workspaceService = new WorkspaceService({ userSettingsResource: URI.file(environmentService.appSettingsPath), configurationCache: new ConfigurationCache(environmentService) }, new ConfigurationFileService(), remoteAgentService);
instantiationService.stub(IWorkspaceContextService, workspaceService);
return workspaceService.initialize(noWorkspace ? { id: '' } : { folder: URI.file(workspaceDir), id: createHash('md5').update(URI.file(workspaceDir).toString()).digest('hex') }).then(() => {
instantiationService.stub(IConfigurationService, workspaceService);
instantiationService.stub(IFileService, new FileService(workspaceService, TestEnvironmentService, new TestTextResourceConfigurationService(), new TestConfigurationService(), new TestLifecycleService(), new TestStorageService(), new TestNotificationService(), { disableWatcher: true }));
const fileService = new FileService2(new NullLogService());
fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService()));
fileService.setLegacyService(new LegacyFileService(
fileService,
workspaceService,
TestEnvironmentService,
new TestTextResourceConfigurationService(),
));
instantiationService.stub(IFileService, fileService);
instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService));
instantiationService.stub(ITextModelService, <ITextModelService>instantiationService.createInstance(TextModelResolverService));
instantiationService.stub(ICommandService, CommandService);
@@ -135,7 +145,7 @@ suite('ConfigurationEditingService', () => {
function clearWorkspace(): Promise<void> {
return new Promise<void>((c, e) => {
if (parentDir) {
extfs.del(parentDir, os.tmpdir(), () => c(undefined), () => c(undefined));
rimraf(parentDir, RimRafMode.MOVE).then(c, c);
} else {
c(undefined);
}

View File

@@ -16,15 +16,14 @@ import { parseArgs } from 'vs/platform/environment/node/argv';
import * as pfs from 'vs/base/node/pfs';
import * as uuid from 'vs/base/common/uuid';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService';
import { ISingleFolderWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
import { ISingleFolderWorkspaceInitializationPayload, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ConfigurationEditingErrorCode } from 'vs/workbench/services/configuration/common/configurationEditingService';
import { IFileService, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files';
import { IFileService } from 'vs/platform/files/common/files';
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
import { ConfigurationTarget, IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { workbenchInstantiationService, TestTextResourceConfigurationService, TestTextFileService, TestLifecycleService, TestEnvironmentService, TestStorageService } from 'vs/workbench/test/workbenchTestServices';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
import { FileService } from 'vs/workbench/services/files/node/fileService';
import { workbenchInstantiationService, TestTextResourceConfigurationService, TestTextFileService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices';
import { LegacyFileService } from 'vs/workbench/services/files/node/fileService';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
@@ -32,15 +31,18 @@ import { TextModelResolverService } from 'vs/workbench/services/textmodelResolve
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';
import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService';
import { createHash } from 'crypto';
import { Emitter, Event } from 'vs/base/common/event';
import { Schemas } from 'vs/base/common/network';
import { originalFSPath } from 'vs/base/common/resources';
import { isLinux } from 'vs/base/common/platform';
import { IWorkspaceIdentifier } from 'vs/workbench/services/configuration/node/configuration';
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl';
import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-browser/remoteAuthorityResolverService';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { FileService2 } from 'vs/workbench/services/files2/common/fileService2';
import { NullLogService } from 'vs/platform/log/common/log';
import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider';
import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache';
import { ConfigurationFileService } from 'vs/workbench/services/configuration/node/configurationFileService';
class SettingsTestEnvironmentService extends EnvironmentService {
@@ -91,7 +93,7 @@ function setUpWorkspace(folders: string[]): Promise<{ parentDir: string, configP
suite('WorkspaceContextService - Folder', () => {
test('getWorkspace()', () => {
// {{SQL CARBON EDIT}} - Remove test
// {{SQL CARBON EDIT}} - Remove tests
assert.equal(0, 0);
});
});