提交 c5486fe7 编写于 作者: S Sandeep Somavarapu

#66907 Support non file workspaces

- File workspaces: Use node services until file service is acquired
- Non file workspaces:  Use cached workspace until file service is acquired
上级 a865e669
......@@ -15,11 +15,10 @@ import { Disposable, IDisposable, dispose } 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 { isLinux } from 'vs/base/common/platform';
import { ConfigWatcher } from 'vs/base/node/config';
import { ConfigurationModel } 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 { IStoredWorkspace, IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import { IStoredWorkspaceFolder, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import * as extfs from 'vs/base/node/extfs';
import { JSONEditingService } from 'vs/workbench/services/configuration/node/jsonEditingService';
import { WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
......@@ -32,94 +31,280 @@ import { IConfigurationModel } from 'vs/platform/configuration/common/configurat
export class WorkspaceConfiguration extends Disposable {
private _workspaceConfigPath: URI;
private _workspaceConfigurationWatcher: ConfigWatcher<WorkspaceConfigurationModelParser>;
private _workspaceConfigurationWatcherDisposables: IDisposable[] = [];
private readonly _cachedConfiguration: CachedWorkspaceConfiguration;
private _workspaceConfiguration: IWorkspaceConfiguration | null;
private _workspaceIdentifier: IWorkspaceIdentifier | null = null;
private _fileService: IFileService | null = null;
private readonly _onDidUpdateConfiguration: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidUpdateConfiguration: Event<void> = this._onDidUpdateConfiguration.event;
private _workspaceConfigurationModelParser: WorkspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(this._workspaceConfigPath ? this._workspaceConfigPath.fsPath : '');
private _cache: ConfigurationModel = new ConfigurationModel();
constructor(
environmentService: IEnvironmentService
) {
super();
this._cachedConfiguration = new CachedWorkspaceConfiguration(environmentService);
this._workspaceConfiguration = this._cachedConfiguration;
}
load(workspaceConfigPath: URI): Promise<void> {
if (this._workspaceConfigPath && this._workspaceConfigPath.fsPath === workspaceConfigPath.fsPath) {
return this.reload();
adopt(fileService: IFileService): Promise<boolean> {
if (!this._fileService) {
this._fileService = fileService;
if (this.adoptWorkspaceConfiguration()) {
if (this._workspaceIdentifier) {
return this._workspaceConfiguration.load(this._workspaceIdentifier).then(() => true);
}
}
}
return Promise.resolve(false);
}
this._workspaceConfigPath = workspaceConfigPath;
return new Promise<void>((c, e) => {
const defaultConfig = new WorkspaceConfigurationModelParser(this._workspaceConfigPath.fsPath);
defaultConfig.parse(JSON.stringify({ folders: [] } as IStoredWorkspace, null, '\t'));
if (this._workspaceConfigurationWatcher) {
this.disposeConfigurationWatcher();
}
this._workspaceConfigurationWatcher = new ConfigWatcher(this._workspaceConfigPath.fsPath, {
changeBufferDelay: 300,
onError: error => errors.onUnexpectedError(error),
defaultConfig,
parse: (content: string, parseErrors: any[]) => {
this._workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(this._workspaceConfigPath.fsPath);
this._workspaceConfigurationModelParser.parse(content);
parseErrors = [...this._workspaceConfigurationModelParser.errors];
this.consolidate();
return this._workspaceConfigurationModelParser;
}, initCallback: () => c(undefined)
});
this.listenToWatcher();
});
load(workspaceIdentifier: IWorkspaceIdentifier): Promise<void> {
this._workspaceIdentifier = workspaceIdentifier;
this.adoptWorkspaceConfiguration();
return this._workspaceConfiguration.load(this._workspaceIdentifier);
}
reload(): Promise<void> {
this.stopListeningToWatcher();
return new Promise<void>(c => this._workspaceConfigurationWatcher.reload(() => {
this.listenToWatcher();
c(undefined);
}));
return this._workspaceIdentifier ? this.load(this._workspaceIdentifier) : Promise.resolve();
}
getFolders(): IStoredWorkspaceFolder[] {
return this._workspaceConfigurationModelParser.folders;
return this._workspaceConfiguration.getFolders();
}
setFolders(folders: IStoredWorkspaceFolder[], jsonEditingService: JSONEditingService): Promise<void> {
return jsonEditingService.write(this._workspaceConfigPath, { key: 'folders', value: folders }, true)
.then(() => this.reload());
if (this._workspaceIdentifier) {
return jsonEditingService.write(URI.file(this._workspaceIdentifier.configPath), { key: 'folders', value: folders }, true)
.then(() => this.reload());
}
return Promise.resolve();
}
getConfiguration(): ConfigurationModel {
return this._cache;
return this._workspaceConfiguration.getWorkspaceSettings();
}
reprocessWorkspaceSettings(): ConfigurationModel {
this._workspaceConfigurationModelParser.reprocessWorkspaceSettings();
this.consolidate();
this._workspaceConfiguration.reprocessWorkspaceSettings();
return this.getConfiguration();
}
private listenToWatcher() {
this._workspaceConfigurationWatcher.onDidUpdateConfiguration(() => this._onDidUpdateConfiguration.fire(), this, this._workspaceConfigurationWatcherDisposables);
private adoptWorkspaceConfiguration(): boolean {
if (this._workspaceIdentifier) {
if (this._fileService) {
if (!(this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration)) {
dispose(this._workspaceConfiguration);
const nodeBasedWorkspaceConfiguration = this._workspaceConfiguration instanceof NodeBasedWorkspaceConfiguration ? this._workspaceConfiguration : undefined;
this._workspaceConfiguration = new FileServiceBasedWorkspaceConfiguration(this._fileService, nodeBasedWorkspaceConfiguration);
this._register(this._workspaceConfiguration.onDidChange(e => this.onDidWorkspaceConfigurationChange()));
return !nodeBasedWorkspaceConfiguration;
}
return false;
}
if (URI.file(this._workspaceIdentifier.configPath).scheme === Schemas.file) {
if (!(this._workspaceConfiguration instanceof NodeBasedWorkspaceConfiguration)) {
dispose(this._workspaceConfiguration);
this._workspaceConfiguration = new NodeBasedWorkspaceConfiguration();
return true;
}
return false;
}
}
return false;
}
private onDidWorkspaceConfigurationChange(): void {
this.updateCache();
this.reload().then(() => this._onDidUpdateConfiguration.fire());
}
private updateCache(): Promise<void> {
if (this._workspaceIdentifier && URI.file(this._workspaceIdentifier.configPath).scheme !== Schemas.file && this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration) {
return this._workspaceConfiguration.load(this._workspaceIdentifier)
.then(() => this._cachedConfiguration.updateWorkspace(this._workspaceIdentifier, this._workspaceConfiguration.getConfigurationModel()));
}
return Promise.resolve(undefined);
}
}
interface IWorkspaceConfiguration extends IDisposable {
readonly onDidChange: Event<void>;
load(workspaceIdentifier: IWorkspaceIdentifier): Promise<void>;
getConfigurationModel(): ConfigurationModel;
getFolders(): IStoredWorkspaceFolder[];
getWorkspaceSettings(): ConfigurationModel;
reprocessWorkspaceSettings(): ConfigurationModel;
}
abstract class AbstractWorkspaceConfiguration extends Disposable implements IWorkspaceConfiguration {
private _workspaceConfigurationModelParser: WorkspaceConfigurationModelParser;
private _workspaceSettings: ConfigurationModel;
private _workspaceIdentifier: IWorkspaceIdentifier | null = null;
protected readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange: Event<void> = this._onDidChange.event;
constructor(from?: AbstractWorkspaceConfiguration) {
super();
this._workspaceConfigurationModelParser = from ? from._workspaceConfigurationModelParser : new WorkspaceConfigurationModelParser('');
this._workspaceSettings = new ConfigurationModel();
}
get workspaceIdentifier(): IWorkspaceIdentifier | null {
return this._workspaceIdentifier;
}
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();
});
}
getConfigurationModel(): ConfigurationModel {
return this._workspaceConfigurationModelParser.configurationModel;
}
getFolders(): IStoredWorkspaceFolder[] {
return this._workspaceConfigurationModelParser.folders;
}
getWorkspaceSettings(): ConfigurationModel {
return this._workspaceSettings;
}
private stopListeningToWatcher() {
this._workspaceConfigurationWatcherDisposables = dispose(this._workspaceConfigurationWatcherDisposables);
reprocessWorkspaceSettings(): ConfigurationModel {
this._workspaceConfigurationModelParser.reprocessWorkspaceSettings();
this.consolidate();
return this.getWorkspaceSettings();
}
private consolidate(): void {
this._cache = this._workspaceConfigurationModelParser.settingsModel.merge(this._workspaceConfigurationModelParser.launchModel);
this._workspaceSettings = this._workspaceConfigurationModelParser.settingsModel.merge(this._workspaceConfigurationModelParser.launchModel);
}
protected abstract loadWorkspaceConfigurationContents(workspaceIdentifier: IWorkspaceIdentifier): Promise<string>;
}
class NodeBasedWorkspaceConfiguration extends AbstractWorkspaceConfiguration {
protected loadWorkspaceConfigurationContents(workspaceIdentifier: IWorkspaceIdentifier): Promise<string> {
return pfs.readFile(workspaceIdentifier.configPath)
.then(contents => contents.toString(), e => {
errors.onUnexpectedError(e);
return '';
});
}
}
class FileServiceBasedWorkspaceConfiguration extends AbstractWorkspaceConfiguration {
private workspaceConfig: URI | null = null;
private readonly reloadConfigurationScheduler: RunOnceScheduler;
constructor(private fileService: IFileService, from?: AbstractWorkspaceConfiguration) {
super(from);
this.workspaceConfig = from ? URI.file(from.workspaceIdentifier.configPath) : null;
this._register(fileService.onFileChanges(e => this.handleWorkspaceFileEvents(e)));
this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50));
}
protected loadWorkspaceConfigurationContents(workspaceIdentifier: IWorkspaceIdentifier): Promise<string> {
this.workspaceConfig = URI.file(workspaceIdentifier.configPath);
return this.fileService.resolveContent(this.workspaceConfig)
.then(content => content.value, e => {
errors.onUnexpectedError(e);
return '';
});
}
private handleWorkspaceFileEvents(event: FileChangesEvent): void {
if (this.workspaceConfig) {
const events = event.changes;
let affectedByChanges = false;
// Find changes that affect workspace file
for (let i = 0, len = events.length; i < len && !affectedByChanges; i++) {
affectedByChanges = resources.isEqual(this.workspaceConfig, events[i].resource);
}
if (affectedByChanges) {
this.reloadConfigurationScheduler.schedule();
}
}
}
}
class CachedWorkspaceConfiguration extends Disposable implements IWorkspaceConfiguration {
private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange: Event<void> = this._onDidChange.event;
private cachedWorkspacePath: string;
private cachedConfigurationPath: string;
private _workspaceConfigurationModelParser: WorkspaceConfigurationModelParser;
private _workspaceSettings: ConfigurationModel;
constructor(private environmentService: IEnvironmentService) {
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);
}, () => null);
}
private disposeConfigurationWatcher(): void {
this.stopListeningToWatcher();
if (this._workspaceConfigurationWatcher) {
this._workspaceConfigurationWatcher.dispose();
getConfigurationModel(): ConfigurationModel {
return this._workspaceConfigurationModelParser.configurationModel;
}
getFolders(): IStoredWorkspaceFolder[] {
return this._workspaceConfigurationModelParser.folders;
}
getWorkspaceSettings(): ConfigurationModel {
return this._workspaceSettings;
}
reprocessWorkspaceSettings(): ConfigurationModel {
return this._workspaceSettings;
}
async updateWorkspace(workspaceIdentifier: IWorkspaceIdentifier, configurationModel: ConfigurationModel): Promise<void> {
try {
this.createPaths(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());
await pfs.writeFile(this.cachedConfigurationPath, raw);
} else {
pfs.rimraf(this.cachedWorkspacePath);
}
} catch (error) {
errors.onUnexpectedError(error);
}
}
dispose(): void {
this.disposeConfigurationWatcher();
super.dispose();
private createPaths(workspaceIdentifier: IWorkspaceIdentifier) {
this.cachedWorkspacePath = paths.join(this.environmentService.userDataPath, 'CachedConfigurations', 'workspaces', workspaceIdentifier.id);
this.cachedConfigurationPath = paths.join(this.cachedWorkspacePath, 'workspace.json');
}
}
......@@ -133,12 +318,11 @@ 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 {
export interface IFolderConfiguration extends IDisposable {
readonly onDidChange: Event<void>;
readonly loaded: boolean;
loadConfiguration(): Promise<ConfigurationModel>;
reprocess(): ConfigurationModel;
dispose(): void;
}
export abstract class AbstractFolderConfiguration extends Disposable implements IFolderConfiguration {
......@@ -370,7 +554,7 @@ export class CachedFolderConfiguration extends Disposable implements IFolderConf
configFolderRelativePath: string,
environmentService: IEnvironmentService) {
super();
this.cachedFolderPath = paths.join(environmentService.userDataPath, 'CachedConfigurations', createHash('md5').update(paths.join(folder.path, configFolderRelativePath)).digest('hex'));
this.cachedFolderPath = paths.join(environmentService.userDataPath, 'CachedConfigurations', 'folders', createHash('md5').update(paths.join(folder.path, configFolderRelativePath)).digest('hex'));
this.cachedConfigurationPath = paths.join(this.cachedFolderPath, 'configuration.json');
this.configurationModel = new ConfigurationModel();
}
......
......@@ -73,7 +73,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
this.defaultConfiguration = new DefaultConfigurationModel();
this.userConfiguration = this._register(new UserConfiguration(environmentService.appSettingsPath));
this.workspaceConfiguration = this._register(new WorkspaceConfiguration());
this.workspaceConfiguration = this._register(new WorkspaceConfiguration(environmentService));
this._register(this.userConfiguration.onDidChangeConfiguration(userConfiguration => this.onUserConfigurationChanged(userConfiguration)));
this._register(this.workspaceConfiguration.onDidUpdateConfiguration(() => this.onWorkspaceConfigurationChanged()));
......@@ -303,14 +303,18 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
acquireFileService(fileService: IFileService): void {
this.fileService = fileService;
const changedWorkspaceFolders: IWorkspaceFolder[] = [];
Promise.all(this.cachedFolderConfigs.values()
Promise.all([this.workspaceConfiguration.adopt(fileService), ...this.cachedFolderConfigs.values()
.map(folderConfiguration => folderConfiguration.adopt(fileService)
.then(result => {
if (result) {
changedWorkspaceFolders.push(folderConfiguration.workspaceFolder);
}
})))
.then(() => {
return result;
}))])
.then(([workspaceChanged]) => {
if (workspaceChanged) {
this.onWorkspaceConfigurationChanged();
}
for (const workspaceFolder of changedWorkspaceFolders) {
this.onWorkspaceFolderConfigurationChanged(workspaceFolder);
}
......@@ -335,9 +339,9 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
}
private createMultiFolderWorkspace(workspaceIdentifier: IWorkspaceIdentifier): Promise<Workspace> {
const workspaceConfigPath = URI.file(workspaceIdentifier.configPath);
return this.workspaceConfiguration.load(workspaceConfigPath)
return this.workspaceConfiguration.load(workspaceIdentifier)
.then(() => {
const workspaceConfigPath = URI.file(workspaceIdentifier.configPath);
const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), URI.file(dirname(workspaceConfigPath.fsPath)));
const workspaceId = workspaceIdentifier.id;
return new Workspace(workspaceId, workspaceFolders, workspaceConfigPath);
......
......@@ -19,7 +19,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur
import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService';
import { ISingleFolderWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
import { ConfigurationEditingErrorCode } from 'vs/workbench/services/configuration/node/configurationEditingService';
import { IFileService } from 'vs/platform/files/common/files';
import { IFileService, FileChangesEvent, FileChangeType } 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';
......@@ -34,6 +34,7 @@ import { JSONEditingService } from 'vs/workbench/services/configuration/node/jso
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { Uri } from 'vscode';
import { createHash } from 'crypto';
import { Emitter, Event } from 'vs/base/common/event';
class SettingsTestEnvironmentService extends EnvironmentService {
......@@ -140,7 +141,7 @@ suite('WorkspaceContextService - Folder', () => {
suite('WorkspaceContextService - Workspace', () => {
let parentResource: string, testObject: WorkspaceService;
let parentResource: string, testObject: WorkspaceService, instantiationService: TestInstantiationService, fileChangeEvent: Emitter<FileChangesEvent> = new Emitter<FileChangesEvent>();
setup(() => {
return setUpWorkspace(['a', 'b'])
......@@ -151,14 +152,16 @@ suite('WorkspaceContextService - Workspace', () => {
const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, path.join(parentDir, 'settings.json'));
const workspaceService = new WorkspaceService(environmentService);
const instantiationService = <TestInstantiationService>workbenchInstantiationService();
instantiationService = <TestInstantiationService>workbenchInstantiationService();
instantiationService.stub(IWorkspaceContextService, workspaceService);
instantiationService.stub(IConfigurationService, workspaceService);
instantiationService.stub(IEnvironmentService, environmentService);
return workspaceService.initialize({ id: configPath, configPath }).then(() => {
const fileService = new FileService(<IWorkspaceContextService>workspaceService, TestEnvironmentService, new TestTextResourceConfigurationService(), workspaceService, new TestLifecycleService(), new TestStorageService(), new TestNotificationService(), { disableWatcher: true });
const fileService = new (class TestFileService extends FileService {
get onFileChanges(): Event<FileChangesEvent> { return fileChangeEvent.event; }
})(<IWorkspaceContextService>workspaceService, TestEnvironmentService, new TestTextResourceConfigurationService(), workspaceService, new TestLifecycleService(), new TestStorageService(), new TestNotificationService(), { disableWatcher: true });
instantiationService.stub(IFileService, fileService);
instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService));
instantiationService.stub(ITextModelService, <ITextModelService>instantiationService.createInstance(TextModelResolverService));
......@@ -293,7 +296,15 @@ suite('WorkspaceContextService - Workspace', () => {
done();
});
const workspace = { folders: [{ path: folders[0].uri.fsPath }, { path: folders[1].uri.fsPath }] };
fs.writeFileSync(testObject.getWorkspace().configuration!.fsPath, JSON.stringify(workspace, null, '\t'));
instantiationService.get(IFileService).updateContent(testObject.getWorkspace().configuration, JSON.stringify(workspace, null, '\t'))
.then(() => {
fileChangeEvent.fire(new FileChangesEvent([
{
resource: testObject.getWorkspace().configuration,
type: FileChangeType.UPDATED
}
]));
}, done);
}, done);
});
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册