Merge from vscode 4636be2b71c87bfb0bfe3c94278b447a5efcc1f1 (#8722)

* Merge from vscode 4636be2b71c87bfb0bfe3c94278b447a5efcc1f1

* remove tests that aren't working
This commit is contained in:
Anthony Dresser
2019-12-18 00:14:28 -08:00
committed by GitHub
parent 0fd870d156
commit 30d9e9c141
289 changed files with 5537 additions and 3039 deletions

View File

@@ -67,22 +67,22 @@ export interface IConfigurationChangeEvent {
export interface IConfigurationValue<T> {
readonly default?: T;
readonly user?: T;
readonly userLocal?: T;
readonly userRemote?: T;
readonly workspace?: T;
readonly workspaceFolder?: T;
readonly memory?: T;
readonly defaultValue?: T;
readonly userValue?: T;
readonly userLocalValue?: T;
readonly userRemoteValue?: T;
readonly workspaceValue?: T;
readonly workspaceFolderValue?: T;
readonly memoryValue?: T;
readonly value?: T;
readonly defaultTarget?: { value?: T, override?: T };
readonly userTarget?: { value?: T, override?: T };
readonly userLocalTarget?: { value?: T, override?: T };
readonly userRemoteTarget?: { value?: T, override?: T };
readonly workspaceTarget?: { value?: T, override?: T };
readonly workspaceFolderTarget?: { value?: T, override?: T };
readonly memoryTarget?: { value?: T, override?: T };
readonly default?: { value?: T, override?: T };
readonly user?: { value?: T, override?: T };
readonly userLocal?: { value?: T, override?: T };
readonly userRemote?: { value?: T, override?: T };
readonly workspace?: { value?: T, override?: T };
readonly workspaceFolder?: { value?: T, override?: T };
readonly memory?: { value?: T, override?: T };
}
export interface IConfigurationService {
@@ -216,14 +216,11 @@ export function compare(from: IConfigurationModel | undefined, to: IConfiguratio
export function toOverrides(raw: any, conflictReporter: (message: string) => void): IOverrides[] {
const overrides: IOverrides[] = [];
const configurationProperties = Registry.as<IConfigurationRegistry>(Extensions.Configuration).getConfigurationProperties();
for (const key of Object.keys(raw)) {
if (OVERRIDE_PROPERTY_PATTERN.test(key)) {
const overrideRaw: any = {};
for (const keyInOverrideRaw in raw[key]) {
if (configurationProperties[keyInOverrideRaw] && configurationProperties[keyInOverrideRaw].overridable) {
overrideRaw[keyInOverrideRaw] = raw[key][keyInOverrideRaw];
}
overrideRaw[keyInOverrideRaw] = raw[key][keyInOverrideRaw];
}
overrides.push({
identifiers: [overrideIdentifierFromKey(key).trim()],
@@ -361,11 +358,11 @@ export function getMigratedSettingValue<T>(configurationService: IConfigurationS
const setting = configurationService.inspect<T>(currentSettingName);
const legacySetting = configurationService.inspect<T>(legacySettingName);
if (typeof setting.user !== 'undefined' || typeof setting.workspace !== 'undefined' || typeof setting.workspaceFolder !== 'undefined') {
if (typeof setting.userValue !== 'undefined' || typeof setting.workspaceValue !== 'undefined' || typeof setting.workspaceFolderValue !== 'undefined') {
return setting.value!;
} else if (typeof legacySetting.user !== 'undefined' || typeof legacySetting.workspace !== 'undefined' || typeof legacySetting.workspaceFolder !== 'undefined') {
} else if (typeof legacySetting.userValue !== 'undefined' || typeof legacySetting.workspaceValue !== 'undefined' || typeof legacySetting.workspaceFolderValue !== 'undefined') {
return legacySetting.value!;
} else {
return setting.default!;
return setting.defaultValue!;
}
}

View File

@@ -394,22 +394,22 @@ export class Configuration {
const value = consolidateConfigurationModel.getValue<C>(key);
return {
default: defaultValue,
user: userValue,
userLocal: userLocalValue,
userRemote: userRemoteValue,
workspace: workspaceValue,
workspaceFolder: workspaceFolderValue,
memory: memoryValue,
defaultValue: defaultValue,
userValue: userValue,
userLocalValue: userLocalValue,
userRemoteValue: userRemoteValue,
workspaceValue: workspaceValue,
workspaceFolderValue: workspaceFolderValue,
memoryValue: memoryValue,
value,
defaultTarget: defaultValue !== undefined ? { value: this._defaultConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this._defaultConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined,
userTarget: userValue !== undefined ? { value: this.userConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.userConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined,
userLocalTarget: userLocalValue !== undefined ? { value: this.localUserConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.localUserConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined,
userRemoteTarget: userRemoteValue !== undefined ? { value: this.remoteUserConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.remoteUserConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined,
workspaceTarget: workspaceValue !== undefined ? { value: this._workspaceConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this._workspaceConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined,
workspaceFolderTarget: workspaceFolderValue !== undefined ? { value: folderConfigurationModel?.freeze().getValue(key), override: overrides.overrideIdentifier ? folderConfigurationModel?.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined,
memoryTarget: memoryValue !== undefined ? { value: memoryConfigurationModel.getValue(key), override: overrides.overrideIdentifier ? memoryConfigurationModel.getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined,
default: defaultValue !== undefined ? { value: this._defaultConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this._defaultConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined,
user: userValue !== undefined ? { value: this.userConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.userConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined,
userLocal: userLocalValue !== undefined ? { value: this.localUserConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.localUserConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined,
userRemote: userRemoteValue !== undefined ? { value: this.remoteUserConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.remoteUserConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined,
workspace: workspaceValue !== undefined ? { value: this._workspaceConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this._workspaceConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined,
workspaceFolder: workspaceFolderValue !== undefined ? { value: folderConfigurationModel?.freeze().getValue(key), override: overrides.overrideIdentifier ? folderConfigurationModel?.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined,
memory: memoryValue !== undefined ? { value: memoryConfigurationModel.getValue(key), override: overrides.overrideIdentifier ? memoryConfigurationModel.getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined,
};
}

View File

@@ -99,6 +99,10 @@ export const enum ConfigurationScope {
* Resource specific configuration, which can be configured in the user, workspace or folder settings.
*/
RESOURCE,
/**
* Resource specific configuration that can also be configured in language specific settings
*/
RESOURCE_LANGUAGE,
/**
* Machine specific configuration that can also be configured in workspace or folder settings.
*/
@@ -106,7 +110,6 @@ export const enum ConfigurationScope {
}
export interface IConfigurationPropertySchema extends IJSONSchema {
overridable?: boolean;
scope?: ConfigurationScope;
included?: boolean;
tags?: string[];
@@ -124,7 +127,6 @@ export interface IConfigurationNode {
description?: string;
properties?: { [path: string]: IConfigurationPropertySchema; };
allOf?: IConfigurationNode[];
overridable?: boolean;
scope?: ConfigurationScope;
extensionInfo?: IConfigurationExtensionInfo;
}
@@ -144,7 +146,8 @@ export const machineOverridableSettings: { properties: SettingProperties, patter
export const windowSettings: { properties: SettingProperties, patternProperties: SettingProperties } = { properties: {}, patternProperties: {} };
export const resourceSettings: { properties: SettingProperties, patternProperties: SettingProperties } = { properties: {}, patternProperties: {} };
export const editorConfigurationSchemaId = 'vscode://schemas/settings/editor';
export const resourceLanguageSettingsSchemaId = 'vscode://schemas/settings/resourceLanguage';
const contributionRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
class ConfigurationRegistry implements IConfigurationRegistry {
@@ -153,7 +156,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
private readonly configurationContributors: IConfigurationNode[];
private readonly configurationProperties: { [qualifiedKey: string]: IJSONSchema };
private readonly excludedConfigurationProperties: { [qualifiedKey: string]: IJSONSchema };
private readonly editorConfigurationSchema: IJSONSchema;
private readonly resourceLanguageSettingsSchema: IJSONSchema;
private readonly overrideIdentifiers: string[] = [];
private overridePropertyPattern: string;
@@ -170,12 +173,12 @@ class ConfigurationRegistry implements IConfigurationRegistry {
properties: {}
};
this.configurationContributors = [this.defaultOverridesConfigurationNode];
this.editorConfigurationSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown editor configuration setting', allowTrailingCommas: true, allowComments: true };
this.resourceLanguageSettingsSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown editor configuration setting', allowTrailingCommas: true, allowComments: true };
this.configurationProperties = {};
this.excludedConfigurationProperties = {};
this.overridePropertyPattern = this.computeOverridePropertyPattern();
contributionRegistry.registerSchema(editorConfigurationSchemaId, this.editorConfigurationSchema);
contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema);
}
public registerConfiguration(configuration: IConfigurationNode, validate: boolean = true): void {
@@ -188,9 +191,9 @@ class ConfigurationRegistry implements IConfigurationRegistry {
properties.push(...this.validateAndRegisterProperties(configuration, validate)); // fills in defaults
this.configurationContributors.push(configuration);
this.registerJSONConfiguration(configuration);
this.updateSchemaForOverrideSettingsConfiguration(configuration);
});
contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema);
this._onDidSchemaChange.fire();
this._onDidUpdateConfiguration.fire(properties);
}
@@ -203,7 +206,6 @@ class ConfigurationRegistry implements IConfigurationRegistry {
properties.push(key);
delete this.configurationProperties[key];
delete this.editorConfigurationSchema.properties![key];
// Delete from schema
delete allSettings.properties[key];
@@ -221,6 +223,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
delete windowSettings.properties[key];
break;
case ConfigurationScope.RESOURCE:
case ConfigurationScope.RESOURCE_LANGUAGE:
delete resourceSettings.properties[key];
break;
}
@@ -238,7 +241,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
}
}
contributionRegistry.registerSchema(editorConfigurationSchemaId, this.editorConfigurationSchema);
contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema);
this._onDidSchemaChange.fire();
this._onDidUpdateConfiguration.fire(properties);
}
@@ -254,7 +257,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
type: 'object',
default: defaultValue,
description: nls.localize('overrideSettings.description', "Configure editor settings to be overridden for {0} language.", key),
$ref: editorConfigurationSchemaId
$ref: resourceLanguageSettingsSchemaId
};
allSettings.properties[key] = propertySchema;
this.defaultOverridesConfigurationNode.properties![key] = propertySchema;
@@ -291,9 +294,8 @@ class ConfigurationRegistry implements IConfigurationRegistry {
this.updateOverridePropertyPatternKey();
}
private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, scope: ConfigurationScope = ConfigurationScope.WINDOW, overridable: boolean = false): string[] {
private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, scope: ConfigurationScope = ConfigurationScope.WINDOW): string[] {
scope = types.isUndefinedOrNull(configuration.scope) ? scope : configuration.scope;
overridable = configuration.overridable || overridable;
let propertyKeys: string[] = [];
let properties = configuration.properties;
if (properties) {
@@ -310,11 +312,6 @@ class ConfigurationRegistry implements IConfigurationRegistry {
if (types.isUndefined(defaultValue)) {
property.default = getDefaultValue(property.type);
}
// Inherit overridable property from parent
if (overridable) {
property.overridable = true;
}
if (OVERRIDE_PROPERTY_PATTERN.test(key)) {
property.scope = undefined; // No scope for overridable properties `[${identifier}]`
} else {
@@ -337,7 +334,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
let subNodes = configuration.allOf;
if (subNodes) {
for (let node of subNodes) {
propertyKeys.push(...this.validateAndRegisterProperties(node, validate, scope, overridable));
propertyKeys.push(...this.validateAndRegisterProperties(node, validate, scope));
}
}
return propertyKeys;
@@ -356,7 +353,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
}
private registerJSONConfiguration(configuration: IConfigurationNode) {
function register(configuration: IConfigurationNode) {
const register = (configuration: IConfigurationNode) => {
let properties = configuration.properties;
if (properties) {
for (const key in properties) {
@@ -377,6 +374,10 @@ class ConfigurationRegistry implements IConfigurationRegistry {
case ConfigurationScope.RESOURCE:
resourceSettings.properties[key] = properties[key];
break;
case ConfigurationScope.RESOURCE_LANGUAGE:
resourceSettings.properties[key] = properties[key];
this.resourceLanguageSettingsSchema.properties![key] = properties[key];
break;
}
}
}
@@ -384,17 +385,10 @@ class ConfigurationRegistry implements IConfigurationRegistry {
if (subNodes) {
subNodes.forEach(register);
}
}
};
register(configuration);
}
private updateSchemaForOverrideSettingsConfiguration(configuration: IConfigurationNode): void {
if (configuration.id !== SETTINGS_OVERRRIDE_NODE_ID) {
this.update(configuration);
contributionRegistry.registerSchema(editorConfigurationSchemaId, this.editorConfigurationSchema);
}
}
private updateOverridePropertyPatternKey(): void {
let patternProperties: IJSONSchema = allSettings.patternProperties[this.overridePropertyPattern];
if (!patternProperties) {
@@ -402,7 +396,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
type: 'object',
description: nls.localize('overrideSettings.defaultDescription', "Configure editor settings to be overridden for a language."),
errorMessage: 'Unknown Identifier. Use language identifiers',
$ref: editorConfigurationSchemaId
$ref: resourceLanguageSettingsSchemaId
};
}
@@ -425,27 +419,11 @@ class ConfigurationRegistry implements IConfigurationRegistry {
this._onDidSchemaChange.fire();
}
private update(configuration: IConfigurationNode): void {
let properties = configuration.properties;
if (properties) {
for (let key in properties) {
if (properties[key].overridable) {
this.editorConfigurationSchema.properties![key] = this.getConfigurationProperties()[key];
}
}
}
let subNodes = configuration.allOf;
if (subNodes) {
subNodes.forEach(subNode => this.update(subNode));
}
}
private computeOverridePropertyPattern(): string {
return this.overrideIdentifiers.length ? OVERRIDE_PATTERN_WITH_SUBSTITUTION.replace('${0}', this.overrideIdentifiers.map(identifier => strings.createRegExp(identifier, false).source).join('|')) : OVERRIDE_PROPERTY;
}
}
const SETTINGS_OVERRRIDE_NODE_ID = 'override';
const OVERRIDE_PROPERTY = '\\[.*\\]$';
const OVERRIDE_PATTERN_WITH_SUBSTITUTION = '\\[(${0})\\]$';
export const OVERRIDE_PROPERTY_PATTERN = new RegExp(OVERRIDE_PROPERTY);

View File

@@ -234,23 +234,6 @@ suite('ConfigurationModel', () => {
suite('CustomConfigurationModel', () => {
suiteSetup(() => {
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
'id': 'a',
'order': 1,
'title': 'a',
'type': 'object',
'properties': {
'a': {
'description': 'a',
'type': 'boolean',
'default': true,
'overridable': true
}
}
});
});
test('simple merge using models', () => {
let base = new ConfigurationModelParser('base');
base.parseContent(JSON.stringify({ 'a': 1, 'b': 2 }));
@@ -346,6 +329,19 @@ suite('CustomConfigurationModel', () => {
});
test('Test registering the same property again', () => {
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
'id': 'a',
'order': 1,
'title': 'a',
'type': 'object',
'properties': {
'a': {
'description': 'a',
'type': 'boolean',
'default': true,
}
}
});
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
'id': 'a',
'order': 1,

View File

@@ -60,8 +60,8 @@ export class TestConfigurationService implements IConfigurationService {
return {
value: getConfigurationValue<T>(config, key),
default: getConfigurationValue<T>(config, key),
user: getConfigurationValue<T>(config, key)
defaultValue: getConfigurationValue<T>(config, key),
userValue: getConfigurationValue<T>(config, key)
};
}

View File

@@ -210,20 +210,20 @@ suite('ConfigurationService - Node', () => {
let res = service.inspect('something.missing');
assert.strictEqual(res.value, undefined);
assert.strictEqual(res.default, undefined);
assert.strictEqual(res.user, undefined);
assert.strictEqual(res.defaultValue, undefined);
assert.strictEqual(res.userValue, undefined);
res = service.inspect('lookup.service.testSetting');
assert.strictEqual(res.default, 'isSet');
assert.strictEqual(res.defaultValue, 'isSet');
assert.strictEqual(res.value, 'isSet');
assert.strictEqual(res.user, undefined);
assert.strictEqual(res.userValue, undefined);
fs.writeFileSync(r.testFile, '{ "lookup.service.testSetting": "bar" }');
await service.reloadConfiguration();
res = service.inspect('lookup.service.testSetting');
assert.strictEqual(res.default, 'isSet');
assert.strictEqual(res.user, 'bar');
assert.strictEqual(res.defaultValue, 'isSet');
assert.strictEqual(res.userValue, 'bar');
assert.strictEqual(res.value, 'bar');
service.dispose();
@@ -247,18 +247,18 @@ suite('ConfigurationService - Node', () => {
service.initialize();
let res = service.inspect('lookup.service.testNullSetting');
assert.strictEqual(res.default, null);
assert.strictEqual(res.defaultValue, null);
assert.strictEqual(res.value, null);
assert.strictEqual(res.user, undefined);
assert.strictEqual(res.userValue, undefined);
fs.writeFileSync(r.testFile, '{ "lookup.service.testNullSetting": null }');
await service.reloadConfiguration();
res = service.inspect('lookup.service.testNullSetting');
assert.strictEqual(res.default, null);
assert.strictEqual(res.defaultValue, null);
assert.strictEqual(res.value, null);
assert.strictEqual(res.user, null);
assert.strictEqual(res.userValue, null);
service.dispose();
return r.cleanUp();

View File

@@ -113,7 +113,7 @@ export class FileService extends Disposable implements IFileService {
if (!provider) {
const error = new Error();
error.name = 'ENOPRO';
error.message = localize('noProviderFound', "No file system provider found for {0}", resource.toString());
error.message = localize('noProviderFound', "No file system provider found for resource '{0}'", resource.toString());
throw error;
}
@@ -128,7 +128,7 @@ export class FileService extends Disposable implements IFileService {
return provider;
}
throw new Error('Provider neither has FileReadWrite, FileReadStream nor FileOpenReadWriteClose capability which is needed for the read operation.');
throw new Error(`Provider for scheme '${resource.scheme}' neither has FileReadWrite, FileReadStream nor FileOpenReadWriteClose capability which is needed for the read operation.`);
}
private async withWriteProvider(resource: URI): Promise<IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability> {
@@ -138,7 +138,7 @@ export class FileService extends Disposable implements IFileService {
return provider;
}
throw new Error('Provider neither has FileReadWrite nor FileOpenReadWriteClose capability which is needed for the write operation.');
throw new Error(`Provider for scheme '${resource.scheme}' neither has FileReadWrite nor FileOpenReadWriteClose capability which is needed for the write operation.`);
}
//#endregion
@@ -160,10 +160,7 @@ export class FileService extends Disposable implements IFileService {
// Specially handle file not found case as file operation result
if (toFileSystemProviderErrorCode(error) === FileSystemProviderErrorCode.FileNotFound) {
throw new FileOperationError(
localize('fileNotFoundError', "File not found ({0})", this.resourceForError(resource)),
FileOperationResult.FILE_NOT_FOUND
);
throw new FileOperationError(localize('fileNotFoundError', "File not found ({0})", this.resourceForError(resource)), FileOperationResult.FILE_NOT_FOUND);
}
// Bubble up any other error as is
@@ -304,7 +301,7 @@ export class FileService extends Disposable implements IFileService {
}
async writeFile(resource: URI, bufferOrReadableOrStream: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: IWriteFileOptions): Promise<IFileStatWithMetadata> {
const provider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(resource));
const provider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(resource), resource);
try {
@@ -338,7 +335,7 @@ export class FileService extends Disposable implements IFileService {
await this.doWriteBuffered(provider, resource, bufferOrReadableOrStream instanceof VSBuffer ? bufferToReadable(bufferOrReadableOrStream) : bufferOrReadableOrStream);
}
} catch (error) {
throw new FileOperationError(localize('err.write', "Unable to write file ({0})", ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options);
throw new FileOperationError(localize('err.write', "Unable to write file '{0}' ({1})", this.resourceForError(resource), ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options);
}
return this.resolve(resource, { resolveMetadata: true });
@@ -354,7 +351,7 @@ export class FileService extends Disposable implements IFileService {
// file cannot be directory
if ((stat.type & FileType.Directory) !== 0) {
throw new FileOperationError(localize('fileIsDirectoryError', "Expected file {0} is actually a directory", this.resourceForError(resource)), FileOperationResult.FILE_IS_DIRECTORY, options);
throw new FileOperationError(localize('fileIsDirectoryError', "Expected file '{0}' is actually a directory", this.resourceForError(resource)), FileOperationResult.FILE_IS_DIRECTORY, options);
}
// Dirty write prevention: if the file on disk has been changed and does not match our expected
@@ -453,14 +450,14 @@ export class FileService extends Disposable implements IFileService {
value: fileStream
};
} catch (error) {
throw new FileOperationError(localize('err.read', "Unable to read file ({0})", ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options);
throw new FileOperationError(localize('err.read', "Unable to read file '{0}' ({1})", this.resourceForError(resource), ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options);
}
}
private readFileStreamed(provider: IFileSystemProviderWithFileReadStreamCapability, resource: URI, token: CancellationToken, options: IReadFileOptions = Object.create(null)): VSBufferReadableStream {
const fileStream = provider.readFileStream(resource, options, token);
return this.transformFileReadStream(fileStream, options);
return this.transformFileReadStream(resource, fileStream, options);
}
private readFileBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, token: CancellationToken, options: IReadFileOptions = Object.create(null)): VSBufferReadableStream {
@@ -469,13 +466,13 @@ export class FileService extends Disposable implements IFileService {
bufferSize: this.BUFFER_SIZE
}, token);
return this.transformFileReadStream(fileStream, options);
return this.transformFileReadStream(resource, fileStream, options);
}
private transformFileReadStream(stream: ReadableStreamEvents<Uint8Array | VSBuffer>, options: IReadFileOptions): VSBufferReadableStream {
private transformFileReadStream(resource: URI, stream: ReadableStreamEvents<Uint8Array | VSBuffer>, options: IReadFileOptions): VSBufferReadableStream {
return transform(stream, {
data: data => data instanceof VSBuffer ? data : VSBuffer.wrap(data),
error: error => new FileOperationError(localize('err.read', "Unable to read file ({0})", ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options)
error: error => new FileOperationError(localize('err.read', "Unable to read file '{0}' ({1})", this.resourceForError(resource), ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options)
}, data => VSBuffer.concat(data));
}
@@ -493,7 +490,7 @@ export class FileService extends Disposable implements IFileService {
}
// Throw if file is too large to load
this.validateReadFileLimits(buffer.byteLength, options);
this.validateReadFileLimits(resource, buffer.byteLength, options);
return bufferToStream(VSBuffer.wrap(buffer));
}
@@ -503,7 +500,7 @@ export class FileService extends Disposable implements IFileService {
// Throw if resource is a directory
if (stat.isDirectory) {
throw new FileOperationError(localize('fileIsDirectoryError', "Expected file {0} is actually a directory", this.resourceForError(resource)), FileOperationResult.FILE_IS_DIRECTORY, options);
throw new FileOperationError(localize('fileIsDirectoryError', "Expected file '{0}' is actually a directory", this.resourceForError(resource)), FileOperationResult.FILE_IS_DIRECTORY, options);
}
// Throw if file not modified since (unless disabled)
@@ -512,12 +509,12 @@ export class FileService extends Disposable implements IFileService {
}
// Throw if file is too large to load
this.validateReadFileLimits(stat.size, options);
this.validateReadFileLimits(resource, stat.size, options);
return stat;
}
private validateReadFileLimits(size: number, options?: IReadFileOptions): void {
private validateReadFileLimits(resource: URI, size: number, options?: IReadFileOptions): void {
if (options?.limits) {
let tooLargeErrorResult: FileOperationResult | undefined = undefined;
@@ -530,7 +527,7 @@ export class FileService extends Disposable implements IFileService {
}
if (typeof tooLargeErrorResult === 'number') {
throw new FileOperationError(localize('fileTooLargeError', "File is too large to open"), tooLargeErrorResult);
throw new FileOperationError(localize('fileTooLargeError', "File '{0}' is too large to open", this.resourceForError(resource)), tooLargeErrorResult);
}
}
}
@@ -540,8 +537,8 @@ export class FileService extends Disposable implements IFileService {
//#region Move/Copy/Delete/Create Folder
async move(source: URI, target: URI, overwrite?: boolean): Promise<IFileStatWithMetadata> {
const sourceProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(source));
const targetProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(target));
const sourceProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(source), source);
const targetProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(target), target);
// move
const mode = await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'move', !!overwrite);
@@ -555,7 +552,7 @@ export class FileService extends Disposable implements IFileService {
async copy(source: URI, target: URI, overwrite?: boolean): Promise<IFileStatWithMetadata> {
const sourceProvider = await this.withReadProvider(source);
const targetProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(target));
const targetProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(target), target);
// copy
const mode = await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'copy', !!overwrite);
@@ -678,11 +675,11 @@ export class FileService extends Disposable implements IFileService {
}
if (isSameResourceWithDifferentPathCase && mode === 'copy') {
throw new Error(localize('unableToMoveCopyError1', "Unable to copy when source is same as target with different path case on a case insensitive file system"));
throw new Error(localize('unableToMoveCopyError1', "Unable to copy when source '{0}' is same as target '{1}' with different path case on a case insensitive file system", this.resourceForError(source), this.resourceForError(target)));
}
if (!isSameResourceWithDifferentPathCase && isEqualOrParent(target, source, !isPathCaseSensitive)) {
throw new Error(localize('unableToMoveCopyError2', "Unable to move/copy when source is parent of target."));
throw new Error(localize('unableToMoveCopyError2', "Unable to move/copy when source '{0}' is parent of target '{1}'.", this.resourceForError(source), this.resourceForError(target)));
}
}
@@ -709,7 +706,7 @@ export class FileService extends Disposable implements IFileService {
}
async createFolder(resource: URI): Promise<IFileStatWithMetadata> {
const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(resource));
const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(resource), resource);
// mkdir recursively
await this.mkdirp(provider, resource);
@@ -729,7 +726,7 @@ export class FileService extends Disposable implements IFileService {
try {
const stat = await provider.stat(directory);
if ((stat.type & FileType.Directory) === 0) {
throw new Error(localize('mkdirExistsError', "{0} exists, but is not a directory", this.resourceForError(directory)));
throw new Error(localize('mkdirExistsError', "Path '{0}' already exists, but is not a directory", this.resourceForError(directory)));
}
break; // we have hit a directory that exists -> good
@@ -756,17 +753,23 @@ export class FileService extends Disposable implements IFileService {
}
async del(resource: URI, options?: { useTrash?: boolean; recursive?: boolean; }): Promise<void> {
const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(resource));
const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(resource), resource);
// Validate trash support
const useTrash = !!options?.useTrash;
if (useTrash && !(provider.capabilities & FileSystemProviderCapabilities.Trash)) {
throw new Error(localize('err.trash', "Provider does not support trash."));
throw new Error(localize('err.trash', "Provider for scheme '{0}' does not support trash.", resource.scheme));
}
// Validate delete
const exists = await this.exists(resource);
if (!exists) {
throw new FileOperationError(localize('fileNotFoundError', "File not found ({0})", this.resourceForError(resource)), FileOperationResult.FILE_NOT_FOUND);
}
// Validate recursive
const recursive = !!options?.recursive;
if (!recursive && await this.exists(resource)) {
if (!recursive && exists) {
const stat = await this.resolve(resource);
if (stat.isDirectory && Array.isArray(stat.children) && stat.children.length > 0) {
throw new Error(localize('deleteFailed', "Unable to delete non-empty folder '{0}'.", this.resourceForError(resource)));
@@ -1050,9 +1053,9 @@ export class FileService extends Disposable implements IFileService {
await this.doWriteUnbuffered(targetProvider, target, buffer);
}
protected throwIfFileSystemIsReadonly<T extends IFileSystemProvider>(provider: T): T {
protected throwIfFileSystemIsReadonly<T extends IFileSystemProvider>(provider: T, resource: URI): T {
if (provider.capabilities & FileSystemProviderCapabilities.Readonly) {
throw new FileOperationError(localize('err.readonly', "Resource can not be modified."), FileOperationResult.FILE_PERMISSION_DENIED);
throw new FileOperationError(localize('err.readonly', "Resource '{0}' can not be modified.", this.resourceForError(resource)), FileOperationResult.FILE_PERMISSION_DENIED);
}
return provider;

View File

@@ -467,6 +467,16 @@ suite('Disk File Service', function () {
assert.ok(event!);
assert.equal(event!.resource.fsPath, resource.fsPath);
assert.equal(event!.operation, FileOperation.DELETE);
let error: Error | undefined = undefined;
try {
await service.del(source.resource);
} catch (e) {
error = e;
}
assert.ok(error);
assert.equal((<FileOperationError>error).fileOperationResult, FileOperationResult.FILE_NOT_FOUND);
});
test('deleteFolder (recursive)', async () => {

View File

@@ -7,28 +7,34 @@ import { IMenubarService, IMenubarData } from 'vs/platform/menubar/node/menubar'
import { Menubar } from 'vs/platform/menubar/electron-main/menubar';
import { ILogService } from 'vs/platform/log/common/log';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
export class MenubarMainService implements IMenubarService {
_serviceBrand: undefined;
private _menubar: Menubar;
private _menubar: Menubar | undefined;
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
@ILogService private readonly logService: ILogService
) {
// Install Menu
this._menubar = this.instantiationService.createInstance(Menubar);
this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => {
this._menubar = this.instantiationService.createInstance(Menubar);
});
}
updateMenubar(windowId: number, menus: IMenubarData): Promise<void> {
this.logService.trace('menubarService#updateMenubar', windowId);
return this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => {
this.logService.trace('menubarService#updateMenubar', windowId);
if (this._menubar) {
this._menubar.updateMenu(menus, windowId);
}
if (this._menubar) {
this._menubar.updateMenu(menus, windowId);
}
return Promise.resolve(undefined);
return undefined;
});
}
}

View File

@@ -17,9 +17,14 @@ export interface ResolvedOptions {
readonly extensionHostEnv?: { [key: string]: string | null };
}
export interface TunnelInformation {
detectedTunnels?: { remote: { port: number, host: string }, localAddress: string }[];
}
export interface ResolverResult {
authority: ResolvedAuthority;
options?: ResolvedOptions;
tunnelInformation?: TunnelInformation;
}
export enum RemoteAuthorityResolverErrorCode {

View File

@@ -6,17 +6,28 @@
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
export const ITunnelService = createDecorator<ITunnelService>('tunnelService');
export interface RemoteTunnel {
readonly tunnelRemotePort: number;
readonly tunnelRemoteHost: string;
readonly tunnelLocalPort: number;
readonly tunnelLocalPort?: number;
readonly localAddress: string;
dispose(): void;
}
export interface TunnelOptions {
remote: { port: number, host: string };
localPort?: number;
name?: string;
}
export interface ITunnelProvider {
forwardPort(tunnelOptions: TunnelOptions): Promise<RemoteTunnel> | undefined;
}
export interface ITunnelService {
_serviceBrand: undefined;
@@ -26,6 +37,7 @@ export interface ITunnelService {
openTunnel(remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined;
closeTunnel(remotePort: number): Promise<void>;
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable;
}
export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address: string, port: number } | undefined {

View File

@@ -3,8 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { ITunnelService, RemoteTunnel, ITunnelProvider } from 'vs/platform/remote/common/tunnel';
import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
export class NoOpTunnelService implements ITunnelService {
_serviceBrand: undefined;
@@ -19,4 +20,7 @@ export class NoOpTunnelService implements ITunnelService {
}
async closeTunnel(_remotePort: number): Promise<void> {
}
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable {
throw new Error('Method not implemented.');
}
}

View File

@@ -29,10 +29,10 @@ registerThemingParticipant((theme, collector) => {
const errorIconForeground = theme.getColor(problemsErrorIconForeground);
if (errorIconForeground) {
collector.addRule(`
.monaco-workbench .zone-widget .codicon-error,
.monaco-workbench .markers-panel .marker-icon.codicon-error,
.monaco-workbench .extensions-viewlet > .extensions .codicon-error,
.monaco-workbench .dialog-box .dialog-message-row .codicon-error {
.monaco-editor .zone-widget .codicon-error,
.markers-panel .marker-icon.codicon-error,
.extensions-viewlet > .extensions .codicon-error,
.monaco-dialog-box .dialog-message-row .codicon-error {
color: ${errorIconForeground};
}
`);
@@ -41,11 +41,11 @@ registerThemingParticipant((theme, collector) => {
const warningIconForeground = theme.getColor(problemsWarningIconForeground);
if (errorIconForeground) {
collector.addRule(`
.monaco-workbench .zone-widget .codicon-warning,
.monaco-workbench .markers-panel .marker-icon.codicon-warning,
.monaco-workbench .extensions-viewlet > .extensions .codicon-warning,
.monaco-workbench .extension-editor .codicon-warning,
.monaco-workbench .dialog-box .dialog-message-row .codicon-warning {
.monaco-editor .zone-widget .codicon-warning,
.markers-panel .marker-icon.codicon-warning,
.extensions-viewlet > .extensions .codicon-warning,
.extension-editor .codicon-warning,
.monaco-dialog-box .dialog-message-row .codicon-warning {
color: ${warningIconForeground};
}
`);
@@ -54,11 +54,11 @@ registerThemingParticipant((theme, collector) => {
const infoIconForeground = theme.getColor(problemsInfoIconForeground);
if (errorIconForeground) {
collector.addRule(`
.monaco-workbench .zone-widget .codicon-info,
.monaco-workbench .markers-panel .marker-icon.codicon-info,
.monaco-workbench .extensions-viewlet > .extensions .codicon-info,
.monaco-workbench .extension-editor .codicon-info,
.monaco-workbench .dialog-box .dialog-message-row .codicon-info {
.monaco-editor .zone-widget .codicon-info,
.markers-panel .marker-icon.codicon-info,
.extensions-viewlet > .extensions .codicon-info,
.extension-editor .codicon-info,
.monaco-dialog-box .dialog-message-row .codicon-info {
color: ${infoIconForeground};
}
`);

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { IWorkspaceStorageChangeEvent, IStorageService, StorageScope, IWillSaveStateEvent, WillSaveStateReason, logStorage } from 'vs/platform/storage/common/storage';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -21,11 +21,11 @@ export class BrowserStorageService extends Disposable implements IStorageService
_serviceBrand: undefined;
private readonly _onDidChangeStorage: Emitter<IWorkspaceStorageChangeEvent> = this._register(new Emitter<IWorkspaceStorageChangeEvent>());
readonly onDidChangeStorage: Event<IWorkspaceStorageChangeEvent> = this._onDidChangeStorage.event;
private readonly _onDidChangeStorage = this._register(new Emitter<IWorkspaceStorageChangeEvent>());
readonly onDidChangeStorage = this._onDidChangeStorage.event;
private readonly _onWillSaveState: Emitter<IWillSaveStateEvent> = this._register(new Emitter<IWillSaveStateEvent>());
readonly onWillSaveState: Event<IWillSaveStateEvent> = this._onWillSaveState.event;
private readonly _onWillSaveState = this._register(new Emitter<IWillSaveStateEvent>());
readonly onWillSaveState = this._onWillSaveState.event;
private globalStorage: IStorage | undefined;
private workspaceStorage: IStorage | undefined;
@@ -37,45 +37,15 @@ export class BrowserStorageService extends Disposable implements IStorageService
private workspaceStorageFile: URI | undefined;
private initializePromise: Promise<void> | undefined;
private periodicSaveScheduler = this._register(new RunOnceScheduler(() => this.collectState(), 5000));
get hasPendingUpdate(): boolean {
return (!!this.globalStorageDatabase && this.globalStorageDatabase.hasPendingUpdate) || (!!this.workspaceStorageDatabase && this.workspaceStorageDatabase.hasPendingUpdate);
}
private readonly periodicFlushScheduler = this._register(new RunOnceScheduler(() => this.doFlushWhenIdle(), 5000 /* every 5s */));
private runWhenIdleDisposable: IDisposable | undefined = undefined;
constructor(
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IFileService private readonly fileService: IFileService
) {
super();
// In the browser we do not have support for long running unload sequences. As such,
// we cannot ask for saving state in that moment, because that would result in a
// long running operation.
// Instead, periodically ask customers to save save. The library will be clever enough
// to only save state that has actually changed.
this.periodicSaveScheduler.schedule();
}
private collectState(): void {
runWhenIdle(() => {
// this event will potentially cause new state to be stored
// since new state will only be created while the document
// has focus, one optimization is to not run this when the
// document has no focus, assuming that state has not changed
//
// another optimization is to not collect more state if we
// have a pending update already running which indicates
// that the connection is either slow or disconnected and
// thus unhealthy.
if (document.hasFocus() && !this.hasPendingUpdate) {
this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE });
}
// repeat
this.periodicSaveScheduler.schedule();
});
}
initialize(payload: IWorkspaceInitializationPayload): Promise<void> {
@@ -109,6 +79,13 @@ export class BrowserStorageService extends Disposable implements IStorageService
this.workspaceStorage.init(),
this.globalStorage.init()
]);
// In the browser we do not have support for long running unload sequences. As such,
// we cannot ask for saving state in that moment, because that would result in a
// long running operation.
// Instead, periodically ask customers to save save. The library will be clever enough
// to only save state that has actually changed.
this.periodicFlushScheduler.schedule();
}
get(key: string, scope: StorageScope, fallbackValue: string): string;
@@ -156,6 +133,40 @@ export class BrowserStorageService extends Disposable implements IStorageService
throw new Error('Migrating storage is currently unsupported in Web');
}
private doFlushWhenIdle(): void {
// Dispose any previous idle runner
this.runWhenIdleDisposable = dispose(this.runWhenIdleDisposable);
// Run when idle
this.runWhenIdleDisposable = runWhenIdle(() => {
// this event will potentially cause new state to be stored
// since new state will only be created while the document
// has focus, one optimization is to not run this when the
// document has no focus, assuming that state has not changed
//
// another optimization is to not collect more state if we
// have a pending update already running which indicates
// that the connection is either slow or disconnected and
// thus unhealthy.
if (document.hasFocus() && !this.hasPendingUpdate) {
this.flush();
}
// repeat
this.periodicFlushScheduler.schedule();
});
}
get hasPendingUpdate(): boolean {
return (!!this.globalStorageDatabase && this.globalStorageDatabase.hasPendingUpdate) || (!!this.workspaceStorageDatabase && this.workspaceStorageDatabase.hasPendingUpdate);
}
flush(): void {
this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE });
}
close(): void {
// We explicitly do not close our DBs because writing data onBeforeUnload()
// can result in unexpected results. Namely, it seems that - even though this
@@ -167,6 +178,12 @@ export class BrowserStorageService extends Disposable implements IStorageService
// get triggered in this phase.
this.dispose();
}
dispose(): void {
this.runWhenIdleDisposable = dispose(this.runWhenIdleDisposable);
super.dispose();
}
}
export class FileStorageDatabase extends Disposable implements IStorageDatabase {

View File

@@ -102,6 +102,13 @@ export interface IStorageService {
* Migrate the storage contents to another workspace.
*/
migrate(toWorkspace: IWorkspaceInitializationPayload): Promise<void>;
/**
* Allows to flush state, e.g. in cases where a shutdown is
* imminent. This will send out the onWillSaveState to ask
* everyone for latest state.
*/
flush(): void;
}
export const enum StorageScope {
@@ -126,10 +133,11 @@ export class InMemoryStorageService extends Disposable implements IStorageServic
_serviceBrand: undefined;
private readonly _onDidChangeStorage: Emitter<IWorkspaceStorageChangeEvent> = this._register(new Emitter<IWorkspaceStorageChangeEvent>());
readonly onDidChangeStorage: Event<IWorkspaceStorageChangeEvent> = this._onDidChangeStorage.event;
private readonly _onDidChangeStorage = this._register(new Emitter<IWorkspaceStorageChangeEvent>());
readonly onDidChangeStorage = this._onDidChangeStorage.event;
readonly onWillSaveState = Event.None;
protected readonly _onWillSaveState = this._register(new Emitter<IWillSaveStateEvent>());
readonly onWillSaveState = this._onWillSaveState.event;
private globalCache: Map<string, string> = new Map<string, string>();
private workspaceCache: Map<string, string> = new Map<string, string>();
@@ -215,6 +223,10 @@ export class InMemoryStorageService extends Disposable implements IStorageServic
async migrate(toWorkspace: IWorkspaceInitializationPayload): Promise<void> {
// not supported
}
flush(): void {
this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE });
}
}
export async function logStorage(global: Map<string, string>, workspace: Map<string, string>, globalPath: string, workspacePath: string): Promise<void> {

View File

@@ -16,6 +16,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import { IWorkspaceInitializationPayload, isWorkspaceIdentifier, isSingleFolderWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
import { onUnexpectedError } from 'vs/base/common/errors';
import { assertIsDefined, assertAllDefined } from 'vs/base/common/types';
import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async';
export class NativeStorageService extends Disposable implements IStorageService {
@@ -38,6 +39,9 @@ export class NativeStorageService extends Disposable implements IStorageService
private initializePromise: Promise<void> | undefined;
private readonly periodicFlushScheduler = this._register(new RunOnceScheduler(() => this.doFlushWhenIdle(), 60000 /* every minute */));
private runWhenIdleDisposable: IDisposable | undefined = undefined;
constructor(
globalStorageDatabase: IStorageDatabase,
@ILogService private readonly logService: ILogService,
@@ -63,10 +67,17 @@ export class NativeStorageService extends Disposable implements IStorageService
}
private async doInitialize(payload: IWorkspaceInitializationPayload): Promise<void> {
// Init all storage locations
await Promise.all([
this.initializeGlobalStorage(),
this.initializeWorkspaceStorage(payload)
]);
// On some OS we do not get enough time to persist state on shutdown (e.g. when
// Windows restarts after applying updates). In other cases, VSCode might crash,
// so we periodically save state to reduce the chance of loosing any state.
this.periodicFlushScheduler.schedule();
}
private initializeGlobalStorage(): Promise<void> {
@@ -185,8 +196,32 @@ export class NativeStorageService extends Disposable implements IStorageService
this.getStorage(scope).delete(key);
}
private doFlushWhenIdle(): void {
// Dispose any previous idle runner
this.runWhenIdleDisposable = dispose(this.runWhenIdleDisposable);
// Run when idle
this.runWhenIdleDisposable = runWhenIdle(() => {
// send event to collect state
this.flush();
// repeat
this.periodicFlushScheduler.schedule();
});
}
flush(): void {
this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE });
}
async close(): Promise<void> {
// Stop periodic scheduler and idle runner as we now collect state normally
this.periodicFlushScheduler.dispose();
this.runWhenIdleDisposable = dispose(this.runWhenIdleDisposable);
// Signal as event so that clients can still store data
this._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN });