Merge from vscode 31e03b8ffbb218a87e3941f2b63a249f061fe0e4 (#4986)

This commit is contained in:
Anthony Dresser
2019-04-10 16:29:23 -07:00
committed by GitHub
parent 18c54f41bd
commit 8315dacda4
320 changed files with 5540 additions and 3822 deletions

View File

@@ -0,0 +1,58 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
import { isWindows } from 'vs/base/common/platform';
import { Emitter, Event } from 'vs/base/common/event';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
export class AccessibilityService implements IAccessibilityService {
_serviceBrand: any;
private _accessibilitySupport = AccessibilitySupport.Unknown;
private readonly _onDidChangeAccessibilitySupport = new Emitter<void>();
readonly onDidChangeAccessibilitySupport: Event<void> = this._onDidChangeAccessibilitySupport.event;
constructor(
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
) { }
alwaysUnderlineAccessKeys(): Promise<boolean> {
if (!isWindows) {
return Promise.resolve(false);
}
return new Promise<boolean>(async (resolve) => {
const Registry = await import('vscode-windows-registry');
let value;
try {
value = Registry.GetStringRegKey('HKEY_CURRENT_USER', 'Control Panel\\Accessibility\\Keyboard Preference', 'On');
} catch {
resolve(false);
}
resolve(value === '1');
});
}
setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void {
if (this._accessibilitySupport === accessibilitySupport) {
return;
}
this._accessibilitySupport = accessibilitySupport;
this._onDidChangeAccessibilitySupport.fire();
}
getAccessibilitySupport(): AccessibilitySupport {
if (this._accessibilitySupport === AccessibilitySupport.Unknown) {
const config = this.environmentService.configuration;
this._accessibilitySupport = (config && config.accessibilitySupport) ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled;
}
return this._accessibilitySupport;
}
}

View File

@@ -5,13 +5,12 @@
import { URI } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IResolveContentOptions, IUpdateContentOptions, ITextSnapshot } from 'vs/platform/files/common/files';
import { IResolveContentOptions, ITextSnapshot } from 'vs/platform/files/common/files';
import { ITextBufferFactory } from 'vs/editor/common/model';
export const IBackupFileService = createDecorator<IBackupFileService>('backupFileService');
export const BACKUP_FILE_RESOLVE_OPTIONS: IResolveContentOptions = { acceptTextOnly: true, encoding: 'utf8' };
export const BACKUP_FILE_UPDATE_OPTIONS: IUpdateContentOptions = { encoding: 'utf8' };
/**
* A service that handles any I/O and state associated with the backup system.

View File

@@ -8,14 +8,14 @@ import * as crypto from 'crypto';
import * as pfs from 'vs/base/node/pfs';
import { URI as Uri } from 'vs/base/common/uri';
import { ResourceQueue } from 'vs/base/common/async';
import { IBackupFileService, BACKUP_FILE_UPDATE_OPTIONS, BACKUP_FILE_RESOLVE_OPTIONS } from 'vs/workbench/services/backup/common/backup';
import { IFileService, ITextSnapshot } from 'vs/platform/files/common/files';
import { IBackupFileService, BACKUP_FILE_RESOLVE_OPTIONS } from 'vs/workbench/services/backup/common/backup';
import { IFileService, ITextSnapshot, TextSnapshotReadable } from 'vs/platform/files/common/files';
import { readToMatchingString } from 'vs/base/node/stream';
import { ITextBufferFactory } from 'vs/editor/common/model';
import { createTextBufferFactoryFromStream, createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel';
import { keys } from 'vs/base/common/map';
import { Schemas } from 'vs/base/common/network';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
export interface IBackupFilesModel {
@@ -29,27 +29,6 @@ export interface IBackupFilesModel {
clear(): void;
}
export class BackupSnapshot implements ITextSnapshot {
private preambleHandled: boolean;
constructor(private snapshot: ITextSnapshot, private preamble: string) { }
read(): string | null {
let value = this.snapshot.read();
if (!this.preambleHandled) {
this.preambleHandled = true;
if (typeof value === 'string') {
value = this.preamble + value;
} else {
value = this.preamble;
}
}
return value;
}
}
export class BackupFilesModel implements IBackupFilesModel {
private cache: { [resource: string]: number /* version ID */ } = Object.create(null);
@@ -114,10 +93,10 @@ export class BackupFileService implements IBackupFileService {
private impl: IBackupFileService;
constructor(
@IWindowService windowService: IWindowService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IFileService fileService: IFileService
) {
const backupWorkspacePath = windowService.getConfiguration().backupPath;
const backupWorkspacePath = environmentService.configuration.backupPath;
if (backupWorkspacePath) {
this.impl = new BackupFileServiceImpl(backupWorkspacePath, fileService);
} else {
@@ -232,7 +211,7 @@ class BackupFileServiceImpl implements IBackupFileService {
const preamble = `${resource.toString()}${BackupFileServiceImpl.META_MARKER}`;
// Update content with value
return this.fileService.updateContent(backupResource, new BackupSnapshot(content, preamble), BACKUP_FILE_UPDATE_OPTIONS).then(() => model.add(backupResource, versionId));
return this.fileService.writeFile(backupResource, new TextSnapshotReadable(content, preamble)).then(() => model.add(backupResource, versionId));
});
});
}

View File

@@ -14,7 +14,7 @@ import { URI as Uri } from 'vs/base/common/uri';
import { BackupFileService, BackupFilesModel, hashPath } from 'vs/workbench/services/backup/node/backupFileService';
import { LegacyFileService } from 'vs/workbench/services/files/node/fileService';
import { TextModel, createTextBufferFactory } from 'vs/editor/common/model/textModel';
import { TestContextService, TestTextResourceConfigurationService, TestEnvironmentService, TestWindowService } from 'vs/workbench/test/workbenchTestServices';
import { TestContextService, TestTextResourceConfigurationService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices';
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
import { Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace';
import { DefaultEndOfLine } from 'vs/editor/common/model';
@@ -24,6 +24,8 @@ import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
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 { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService';
import { parseArgs } from 'vs/platform/environment/node/argv';
const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backupfileservice');
const backupHome = path.join(parentDir, 'Backups');
@@ -38,18 +40,18 @@ const fooBackupPath = path.join(workspaceBackupPath, 'file', hashPath(fooFile));
const barBackupPath = path.join(workspaceBackupPath, 'file', hashPath(barFile));
const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', hashPath(untitledFile));
class TestBackupWindowService extends TestWindowService {
class TestBackupEnvironmentService extends WorkbenchEnvironmentService {
private config: IWindowConfiguration;
constructor(workspaceBackupPath: string) {
super();
super(parseArgs(process.argv) as IWindowConfiguration, process.execPath);
this.config = Object.create(null);
this.config.backupPath = workspaceBackupPath;
}
getConfiguration(): IWindowConfiguration {
get configuration(): IWindowConfiguration {
return this.config;
}
}
@@ -64,9 +66,9 @@ class TestBackupFileService extends BackupFileService {
TestEnvironmentService,
new TestTextResourceConfigurationService(),
));
const windowService = new TestBackupWindowService(workspaceBackupPath);
const environmentService = new TestBackupEnvironmentService(workspaceBackupPath);
super(windowService, fileService);
super(environmentService, fileService);
}
public toBackupResource(resource: Uri): Uri {

View File

@@ -25,7 +25,7 @@ class BroadcastService extends Disposable implements IBroadcastService {
) {
super();
this.windowId = windowService.getCurrentWindowId();
this.windowId = windowService.windowId;
this.registerListeners();
}

View File

@@ -11,8 +11,8 @@ import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/l
import { RunOnceScheduler } from 'vs/base/common/async';
import { FileChangeType, FileChangesEvent } 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, IConfigurationCache, ConfigurationKey, IConfigurationFileService } from 'vs/workbench/services/configuration/common/configuration';
import { WorkspaceConfigurationModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels';
import { FOLDER_SETTINGS_PATH, TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, IConfigurationFileService, MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES } 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';
@@ -47,7 +47,7 @@ export class RemoteUserConfiguration extends Disposable {
if (environment) {
this._userConfiguration.dispose();
this._userConfigurationDisposable.dispose();
this._userConfiguration = this._register(new UserConfiguration(environment.appSettingsPath, this._configurationFileService));
this._userConfiguration = this._register(new UserConfiguration(environment.settingsPath, MACHINE_SCOPES, this._configurationFileService));
this._userConfigurationDisposable = this._register(this._userConfiguration.onDidChangeConfiguration(configurationModel => this.onDidUserConfigurationChange(configurationModel)));
this._userConfiguration.initialize().then(configurationModel => this.onDidUserConfigurationChange(configurationModel));
}
@@ -62,6 +62,10 @@ export class RemoteUserConfiguration extends Disposable {
return this._userConfiguration.reload();
}
reprocess(): ConfigurationModel {
return this._userConfiguration.reprocess();
}
private onDidUserConfigurationChange(configurationModel: ConfigurationModel): void {
this.updateCache(configurationModel);
this._onDidChangeConfiguration.fire(configurationModel);
@@ -74,6 +78,7 @@ export class RemoteUserConfiguration extends Disposable {
export class UserConfiguration extends Disposable {
private readonly parser: ConfigurationModelParser;
private readonly reloadConfigurationScheduler: RunOnceScheduler;
protected readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());
readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;
@@ -83,10 +88,12 @@ export class UserConfiguration extends Disposable {
constructor(
private readonly configurationResource: URI,
private readonly scopes: ConfigurationScope[] | undefined,
private readonly configurationFileService: IConfigurationFileService
) {
super();
this.parser = new ConfigurationModelParser(this.configurationResource.toString(), this.scopes);
this._register(configurationFileService.onFileChanges(e => this.handleFileEvents(e)));
this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50));
this._register(toDisposable(() => {
@@ -127,14 +134,18 @@ export class UserConfiguration extends Disposable {
async reload(): Promise<ConfigurationModel> {
try {
const content = await this.configurationFileService.resolveContent(this.configurationResource);
const parser = new ConfigurationModelParser(this.configurationResource.toString());
parser.parse(content);
return parser.configurationModel;
this.parser.parseContent(content);
return this.parser.configurationModel;
} catch (e) {
return new ConfigurationModel();
}
}
reprocess(): ConfigurationModel {
this.parser.parse();
return this.parser.configurationModel;
}
private async onWatchStarted(currentModel: ConfigurationModel): Promise<void> {
const configuraitonModel = await this.reload();
const { added, removed, updated } = compare(currentModel, configuraitonModel);
@@ -202,6 +213,10 @@ class CachedUserConfiguration extends Disposable {
return this.reload();
}
reprocess(): ConfigurationModel {
return this.configurationModel;
}
async reload(): Promise<ConfigurationModel> {
const content = await this.configurationCache.read(this.key);
try {
@@ -371,7 +386,7 @@ class FileServiceBasedWorkspaceConfiguration extends Disposable implements IWork
errors.onUnexpectedError(error);
}
}
this.workspaceConfigurationModelParser.parse(contents);
this.workspaceConfigurationModelParser.parseContent(contents);
this.consolidate();
}
@@ -449,7 +464,7 @@ class CachedWorkspaceConfiguration extends Disposable implements IWorkspaceConfi
const key = this.getKey(workspaceIdentifier);
const contents = await this.configurationCache.read(key);
this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(key.key);
this.workspaceConfigurationModelParser.parse(contents);
this.workspaceConfigurationModelParser.parseContent(contents);
this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel);
} catch (e) {
}
@@ -503,7 +518,7 @@ export interface IFolderConfiguration extends IDisposable {
class FileServiceBasedFolderConfiguration extends Disposable implements IFolderConfiguration {
private _folderSettingsModelParser: FolderSettingsModelParser;
private _folderSettingsModelParser: ConfigurationModelParser;
private _standAloneConfigurations: ConfigurationModel[];
private _cache: ConfigurationModel;
@@ -518,7 +533,7 @@ class FileServiceBasedFolderConfiguration extends Disposable implements IFolderC
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 = new FolderSettingsModelParser(FOLDER_SETTINGS_PATH, WorkbenchState.WORKSPACE === workbenchState ? [ConfigurationScope.RESOURCE] : [ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE]);
this._folderSettingsModelParser = new ConfigurationModelParser(FOLDER_SETTINGS_PATH, WorkbenchState.WORKSPACE === workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES);
this._standAloneConfigurations = [];
this._cache = new ConfigurationModel();
@@ -544,17 +559,17 @@ class FileServiceBasedFolderConfiguration extends Disposable implements IFolderC
// reset
this._standAloneConfigurations = [];
this._folderSettingsModelParser.parse('');
this._folderSettingsModelParser.parseContent('');
// parse
if (configurationContents[0]) {
this._folderSettingsModelParser.parse(configurationContents[0]);
this._folderSettingsModelParser.parseContent(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);
standAloneConfigurationModelParser.parseContent(contents);
this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel);
}
}
@@ -567,7 +582,7 @@ class FileServiceBasedFolderConfiguration extends Disposable implements IFolderC
reprocess(): ConfigurationModel {
const oldContents = this._folderSettingsModelParser.configurationModel.contents;
this._folderSettingsModelParser.reprocess();
this._folderSettingsModelParser.parse();
if (!equals(oldContents, this._folderSettingsModelParser.configurationModel.contents)) {
this.consolidate();
}

View File

@@ -16,7 +16,7 @@ import { isLinux } from 'vs/base/common/platform';
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, IConfigurationCache, IConfigurationFileService } from 'vs/workbench/services/configuration/common/configuration';
import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, IConfigurationFileService, machineSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration';
import { Registry } from 'vs/platform/registry/common/platform';
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';
@@ -72,7 +72,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
this.defaultConfiguration = new DefaultConfigurationModel();
this.configurationCache = configurationCache;
if (userSettingsResource) {
this.localUserConfiguration = this._register(new UserConfiguration(userSettingsResource, configurationFileService));
this.localUserConfiguration = this._register(new UserConfiguration(userSettingsResource, undefined, configurationFileService));
this._register(this.localUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onLocalUserConfigurationChanged(userConfiguration)));
}
if (remoteAuthority) {
@@ -80,7 +80,12 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
this._register(this.remoteUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onRemoteUserConfigurationChanged(userConfiguration)));
}
this.workspaceConfiguration = this._register(new WorkspaceConfiguration(configurationCache, this.configurationFileService));
this._register(this.workspaceConfiguration.onDidUpdateConfiguration(() => this.onWorkspaceConfigurationChanged()));
this._register(this.workspaceConfiguration.onDidUpdateConfiguration(() => {
this.onWorkspaceConfigurationChanged();
if (this.workspaceConfiguration.loaded) {
this.releaseWorkspaceBarrier();
}
}));
this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidSchemaChange(e => this.registerConfigurationSchemas()));
this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidUpdateConfiguration(configurationProperties => this.onDefaultConfigurationChanged(configurationProperties)));
@@ -473,6 +478,9 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
this.registerConfigurationSchemas();
if (this.workspace && this._configuration) {
this._configuration.updateDefaultConfiguration(this.defaultConfiguration);
if (this.remoteUserConfiguration) {
this._configuration.updateRemoteUserConfiguration(this.remoteUserConfiguration.reprocess());
}
if (this.getWorkbenchState() === WorkbenchState.FOLDER) {
this._configuration.updateWorkspaceConfiguration(this.cachedFolderConfigs.get(this.workspace.folders[0].uri)!.reprocess());
} else {
@@ -500,6 +508,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
jsonRegistry.registerSchema(defaultSettingsSchemaId, allSettingsSchema);
jsonRegistry.registerSchema(userSettingsSchemaId, allSettingsSchema);
jsonRegistry.registerSchema(machineSettingsSchemaId, workspaceSettingsSchema);
if (WorkbenchState.WORKSPACE === this.getWorkbenchState()) {
const unsupportedWindowSettings = convertToNotSuggestedProperties(windowSettings.properties, localize('unsupportedWindowSetting', "This setting cannot be applied now. It will be applied when you open this folder directly."));
@@ -539,9 +548,6 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
this.triggerConfigurationChange(workspaceConfigurationChangeEvent, ConfigurationTarget.WORKSPACE);
}
}
if (this.workspaceConfiguration.loaded) {
this.releaseWorkspaceBarrier();
}
return Promise.resolve(undefined);
}

View File

@@ -7,6 +7,7 @@ import { URI } from 'vs/base/common/uri';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Event } from 'vs/base/common/event';
import { FileChangesEvent, IFileService } from 'vs/platform/files/common/files';
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
export const FOLDER_CONFIG_FOLDER_NAME = '.azuredatastudio';
export const FOLDER_SETTINGS_NAME = 'settings';
@@ -14,10 +15,15 @@ export const FOLDER_SETTINGS_PATH = `${FOLDER_CONFIG_FOLDER_NAME}/${FOLDER_SETTI
export const defaultSettingsSchemaId = 'vscode://schemas/settings/default';
export const userSettingsSchemaId = 'vscode://schemas/settings/user';
export const machineSettingsSchemaId = 'vscode://schemas/settings/machine';
export const workspaceSettingsSchemaId = 'vscode://schemas/settings/workspace';
export const folderSettingsSchemaId = 'vscode://schemas/settings/folder';
export const launchSchemaId = 'vscode://schemas/launch';
export const MACHINE_SCOPES = [ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE];
export const WORKSPACE_SCOPES = [ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE];
export const FOLDER_SCOPES = [ConfigurationScope.RESOURCE];
export const TASKS_CONFIGURATION_KEY = 'tasks';
export const LAUNCH_CONFIGURATION_KEY = 'launch';
@@ -25,7 +31,6 @@ 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 {

View File

@@ -138,7 +138,7 @@ export class ConfigurationEditingService {
this.queue = new Queue<void>();
remoteAgentService.getEnvironment().then(environment => {
if (environment) {
this.remoteSettingsResource = environment.appSettingsPath;
this.remoteSettingsResource = environment.settingsPath;
}
});
}
@@ -375,7 +375,7 @@ export class ConfigurationEditingService {
private async resolveModelReference(resource: URI): Promise<IReference<IResolvedTextEditorModel>> {
const exists = await this.fileService.exists(resource);
if (!exists) {
await this.fileService.updateContent(resource, '{}', { encoding: 'utf8' });
await this.textFileService.write(resource, '{}');
}
return this.textModelResolverService.createModelReference(resource);
}

View File

@@ -6,22 +6,21 @@
import { equals } from 'vs/base/common/objects';
import { compare, toValuesTree, IConfigurationChangeEvent, ConfigurationTarget, IConfigurationModel, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration';
import { Configuration as BaseConfiguration, ConfigurationModelParser, ConfigurationChangeEvent, ConfigurationModel, AbstractConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, IConfigurationPropertySchema, Extensions, ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry';
import { IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import { Workspace } from 'vs/platform/workspace/common/workspace';
import { ResourceMap } from 'vs/base/common/map';
import { URI } from 'vs/base/common/uri';
import { WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration';
export class WorkspaceConfigurationModelParser extends ConfigurationModelParser {
private _folders: IStoredWorkspaceFolder[] = [];
private _settingsModelParser: FolderSettingsModelParser;
private _settingsModelParser: ConfigurationModelParser;
private _launchModel: ConfigurationModel;
constructor(name: string) {
super(name);
this._settingsModelParser = new FolderSettingsModelParser(name, [ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE]);
this._settingsModelParser = new ConfigurationModelParser(name, WORKSPACE_SCOPES);
this._launchModel = new ConfigurationModel();
}
@@ -38,14 +37,14 @@ export class WorkspaceConfigurationModelParser extends ConfigurationModelParser
}
reprocessWorkspaceSettings(): void {
this._settingsModelParser.reprocess();
this._settingsModelParser.parse();
}
protected parseRaw(raw: any): IConfigurationModel {
protected doParseRaw(raw: any): IConfigurationModel {
this._folders = (raw['folders'] || []) as IStoredWorkspaceFolder[];
this._settingsModelParser.parse(raw['settings']);
this._settingsModelParser.parseRaw(raw['settings']);
this._launchModel = this.createConfigurationModelFrom(raw, 'launch');
return super.parseRaw(raw);
return super.doParseRaw(raw);
}
private createConfigurationModelFrom(raw: any, key: string): ConfigurationModel {
@@ -67,7 +66,7 @@ export class StandaloneConfigurationModelParser extends ConfigurationModelParser
super(name);
}
protected parseRaw(raw: any): IConfigurationModel {
protected doParseRaw(raw: any): IConfigurationModel {
const contents = toValuesTree(raw, message => console.error(`Conflict in settings file ${this._name}: ${message}`));
const scopedContents = Object.create(null);
scopedContents[this.scope] = contents;
@@ -77,56 +76,6 @@ export class StandaloneConfigurationModelParser extends ConfigurationModelParser
}
export class FolderSettingsModelParser extends ConfigurationModelParser {
private _raw: any;
private _settingsModel: ConfigurationModel;
constructor(name: string, private scopes: ConfigurationScope[]) {
super(name);
}
parse(content: string | any): void {
this._raw = typeof content === 'string' ? this.parseContent(content) : content;
this.parseWorkspaceSettings(this._raw);
}
get configurationModel(): ConfigurationModel {
return this._settingsModel || new ConfigurationModel();
}
reprocess(): void {
this.parse(this._raw);
}
private parseWorkspaceSettings(rawSettings: any): void {
const configurationProperties = Registry.as<IConfigurationRegistry>(Extensions.Configuration).getConfigurationProperties();
const rawWorkspaceSettings = this.filterByScope(rawSettings, configurationProperties, true);
const configurationModel = this.parseRaw(rawWorkspaceSettings);
this._settingsModel = new ConfigurationModel(configurationModel.contents, configurationModel.keys, configurationModel.overrides);
}
private filterByScope(properties: {}, configurationProperties: { [qualifiedKey: string]: IConfigurationPropertySchema }, filterOverriddenProperties: boolean): {} {
const result = {};
for (let key in properties) {
if (OVERRIDE_PROPERTY_PATTERN.test(key) && filterOverriddenProperties) {
result[key] = this.filterByScope(properties[key], configurationProperties, false);
} else {
const scope = this.getScope(key, configurationProperties);
if (this.scopes.indexOf(scope) !== -1) {
result[key] = properties[key];
}
}
}
return result;
}
private getScope(key: string, configurationProperties: { [qualifiedKey: string]: IConfigurationPropertySchema }): ConfigurationScope {
const propertySchema = configurationProperties[key];
return propertySchema && typeof propertySchema.scope !== 'undefined' ? propertySchema.scope : ConfigurationScope.WINDOW;
}
}
export class Configuration extends BaseConfiguration {
constructor(

View File

@@ -86,7 +86,7 @@ export class JSONEditingService implements IJSONEditingService {
private async resolveModelReference(resource: URI): Promise<IReference<IResolvedTextEditorModel>> {
const exists = await this.fileService.exists(resource);
if (!exists) {
await this.fileService.updateContent(resource, '{}', { encoding: 'utf8' });
await this.textFileService.write(resource, '{}');
}
return this.textModelResolverService.createModelReference(resource);
}

View File

@@ -5,10 +5,10 @@
import * as assert from 'assert';
import { join } from 'vs/base/common/path';
import { Registry } from 'vs/platform/registry/common/platform';
import { FolderSettingsModelParser, WorkspaceConfigurationChangeEvent, StandaloneConfigurationModelParser, AllKeysConfigurationChangeEvent, Configuration } from 'vs/workbench/services/configuration/common/configurationModels';
import { WorkspaceConfigurationChangeEvent, StandaloneConfigurationModelParser, AllKeysConfigurationChangeEvent, Configuration } from 'vs/workbench/services/configuration/common/configurationModels';
import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { URI } from 'vs/base/common/uri';
import { ConfigurationChangeEvent, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels';
import { ConfigurationChangeEvent, ConfigurationModel, ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { ResourceMap } from 'vs/base/common/map';
@@ -41,33 +41,33 @@ suite('FolderSettingsModelParser', () => {
});
test('parse all folder settings', () => {
const testObject = new FolderSettingsModelParser('settings', [ConfigurationScope.RESOURCE, ConfigurationScope.WINDOW]);
const testObject = new ConfigurationModelParser('settings', [ConfigurationScope.RESOURCE, ConfigurationScope.WINDOW]);
testObject.parse(JSON.stringify({ 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.application': 'executable' }));
testObject.parseContent(JSON.stringify({ 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.application': 'executable' }));
assert.deepEqual(testObject.configurationModel.contents, { 'FolderSettingsModelParser': { 'window': 'window', 'resource': 'resource' } });
});
test('parse resource folder settings', () => {
const testObject = new FolderSettingsModelParser('settings', [ConfigurationScope.RESOURCE]);
const testObject = new ConfigurationModelParser('settings', [ConfigurationScope.RESOURCE]);
testObject.parse(JSON.stringify({ 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.application': 'executable' }));
testObject.parseContent(JSON.stringify({ 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.application': 'executable' }));
assert.deepEqual(testObject.configurationModel.contents, { 'FolderSettingsModelParser': { 'resource': 'resource' } });
});
test('parse overridable resource settings', () => {
const testObject = new FolderSettingsModelParser('settings', [ConfigurationScope.RESOURCE]);
const testObject = new ConfigurationModelParser('settings', [ConfigurationScope.RESOURCE]);
testObject.parse(JSON.stringify({ '[json]': { 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.application': 'executable' } }));
testObject.parseContent(JSON.stringify({ '[json]': { 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.application': 'executable' } }));
assert.deepEqual(testObject.configurationModel.overrides, [{ 'contents': { 'FolderSettingsModelParser': { 'resource': 'resource' } }, 'identifiers': ['json'] }]);
});
test('reprocess folder settings excludes application setting', () => {
const testObject = new FolderSettingsModelParser('settings', [ConfigurationScope.RESOURCE, ConfigurationScope.WINDOW]);
const testObject = new ConfigurationModelParser('settings', [ConfigurationScope.RESOURCE, ConfigurationScope.WINDOW]);
testObject.parse(JSON.stringify({ 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.anotherApplicationSetting': 'executable' }));
testObject.parseContent(JSON.stringify({ 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.anotherApplicationSetting': 'executable' }));
assert.deepEqual(testObject.configurationModel.contents, { 'FolderSettingsModelParser': { 'resource': 'resource', 'anotherApplicationSetting': 'executable' } });
@@ -84,7 +84,7 @@ suite('FolderSettingsModelParser', () => {
}
});
testObject.reprocess();
testObject.parse();
assert.deepEqual(testObject.configurationModel.contents, { 'FolderSettingsModelParser': { 'resource': 'resource' } });
});
@@ -95,7 +95,7 @@ suite('StandaloneConfigurationModelParser', () => {
test('parse tasks stand alone configuration model', () => {
const testObject = new StandaloneConfigurationModelParser('tasks', 'tasks');
testObject.parse(JSON.stringify({ 'version': '1.1.1', 'tasks': [] }));
testObject.parseContent(JSON.stringify({ 'version': '1.1.1', 'tasks': [] }));
assert.deepEqual(testObject.configurationModel.contents, { 'tasks': { 'version': '1.1.1', 'tasks': [] } });
});

View File

@@ -10,7 +10,7 @@ import * as Types from 'vs/base/common/types';
import { Schemas } from 'vs/base/common/network';
import { toResource } from 'vs/workbench/common/editor';
import { IStringDictionary, forEach, fromMap } from 'vs/base/common/collections';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IWorkspaceFolder, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
@@ -19,22 +19,21 @@ import { AbstractVariableResolverService } from 'vs/workbench/services/configura
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { IQuickInputService, IInputOptions, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput';
import { ConfiguredInput, IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ConfiguredInput } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { IProcessEnvironment } from 'vs/base/common/platform';
export class ConfigurationResolverService extends AbstractVariableResolverService {
export abstract class BaseConfigurationResolverService extends AbstractVariableResolverService {
static INPUT_OR_COMMAND_VARIABLES_PATTERN = /\${((input|command):(.*?))}/g;
constructor(
@IWindowService windowService: IWindowService,
@IEditorService editorService: IEditorService,
@IEnvironmentService environmentService: IEnvironmentService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@ICommandService private readonly commandService: ICommandService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IQuickInputService private readonly quickInputService: IQuickInputService
envVariables: IProcessEnvironment,
editorService: IEditorService,
environmentService: IWorkbenchEnvironmentService,
private readonly configurationService: IConfigurationService,
private readonly commandService: ICommandService,
private readonly workspaceContextService: IWorkspaceContextService,
private readonly quickInputService: IQuickInputService
) {
super({
getFolderUri: (folderName: string): uri | undefined => {
@@ -83,7 +82,7 @@ export class ConfigurationResolverService extends AbstractVariableResolverServic
}
return undefined;
}
}, windowService.getConfiguration().userEnv);
}, envVariables);
}
public resolveWithInteractionReplace(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary<string>): Promise<any> {
@@ -200,7 +199,7 @@ export class ConfigurationResolverService extends AbstractVariableResolverServic
private findVariables(object: any, variables: string[]) {
if (typeof object === 'string') {
let matches;
while ((matches = ConfigurationResolverService.INPUT_OR_COMMAND_VARIABLES_PATTERN.exec(object)) !== null) {
while ((matches = BaseConfigurationResolverService.INPUT_OR_COMMAND_VARIABLES_PATTERN.exec(object)) !== null) {
if (matches.length === 4) {
const command = matches[1];
if (variables.indexOf(command) < 0) {
@@ -293,4 +292,16 @@ export class ConfigurationResolverService extends AbstractVariableResolverServic
}
}
registerSingleton(IConfigurationResolverService, ConfigurationResolverService, true);
export class ConfigurationResolverService extends BaseConfigurationResolverService {
constructor(
@IEditorService editorService: IEditorService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IConfigurationService configurationService: IConfigurationService,
@ICommandService commandService: ICommandService,
@IWorkspaceContextService workspaceContextService: IWorkspaceContextService,
@IQuickInputService quickInputService: IQuickInputService
) {
super(environmentService.configuration.userEnv, editorService, environmentService, configurationService, commandService, workspaceContextService, quickInputService);
}
}

View File

@@ -0,0 +1,31 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { BaseConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService';
export class ConfigurationResolverService extends BaseConfigurationResolverService {
constructor(
@IEditorService editorService: IEditorService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IConfigurationService configurationService: IConfigurationService,
@ICommandService commandService: ICommandService,
@IWorkspaceContextService workspaceContextService: IWorkspaceContextService,
@IQuickInputService quickInputService: IQuickInputService
) {
super(process.env as IProcessEnvironment, editorService, environmentService, configurationService, commandService, workspaceContextService, quickInputService);
}
}
registerSingleton(IConfigurationResolverService, ConfigurationResolverService, true);

View File

@@ -11,15 +11,18 @@ import { ICommandService } from 'vs/platform/commands/common/commands';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { ConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { TestEnvironmentService, TestEditorService, TestContextService, TestWindowService } from 'vs/workbench/test/workbenchTestServices';
import { TestEditorService, TestContextService } from 'vs/workbench/test/workbenchTestServices';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { Disposable } from 'vs/base/common/lifecycle';
import { IQuickInputService, IQuickPickItem, QuickPickInput, IPickOptions, Omit, IInputOptions, IQuickInputButton, IQuickPick, IInputBox, IQuickNavigateConfiguration } from 'vs/platform/quickinput/common/quickInput';
import { CancellationToken } from 'vs/base/common/cancellation';
import * as Types from 'vs/base/common/types';
import { IWindowService, IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { EditorType } from 'vs/editor/common/editorCommon';
import { Selection } from 'vs/editor/common/core/selection';
import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService';
import { parseArgs } from 'vs/platform/environment/node/argv';
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
const mockLineNumber = 10;
class TestEditorServiceWithActiveEditor extends TestEditorService {
@@ -38,7 +41,7 @@ class TestEditorServiceWithActiveEditor extends TestEditorService {
suite('Configuration Resolver Service', () => {
let configurationResolverService: IConfigurationResolverService | null;
let envVariables: { [key: string]: string } = { key1: 'Value for key1', key2: 'Value for key2' };
let windowService: IWindowService;
let environmentService: IWorkbenchEnvironmentService;
let mockCommandService: MockCommandService;
let editorService: TestEditorServiceWithActiveEditor;
let workspace: IWorkspaceFolder;
@@ -48,14 +51,14 @@ suite('Configuration Resolver Service', () => {
mockCommandService = new MockCommandService();
editorService = new TestEditorServiceWithActiveEditor();
quickInputService = new MockQuickInputService();
windowService = new MockWindowService(envVariables);
environmentService = new MockWorkbenchEnvironmentService(envVariables);
workspace = {
uri: uri.parse('file:///VSCode/workspaceLocation'),
name: 'hey',
index: 0,
toResource: (path: string) => uri.file(path)
};
configurationResolverService = new ConfigurationResolverService(windowService, editorService, TestEnvironmentService, new MockInputsConfigurationService(), mockCommandService, new TestContextService(), quickInputService);
configurationResolverService = new ConfigurationResolverService(editorService, environmentService, new MockInputsConfigurationService(), mockCommandService, new TestContextService(), quickInputService);
});
teardown(() => {
@@ -134,7 +137,7 @@ suite('Configuration Resolver Service', () => {
}
});
let service = new ConfigurationResolverService(windowService, new TestEditorServiceWithActiveEditor(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService(), quickInputService);
let service = new ConfigurationResolverService(new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService);
assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} xyz'), 'abc foo xyz');
});
@@ -151,7 +154,7 @@ suite('Configuration Resolver Service', () => {
}
});
let service = new ConfigurationResolverService(windowService, new TestEditorServiceWithActiveEditor(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService(), quickInputService);
let service = new ConfigurationResolverService(new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService);
assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} xyz'), 'abc foo bar xyz');
});
@@ -168,7 +171,7 @@ suite('Configuration Resolver Service', () => {
}
});
let service = new ConfigurationResolverService(windowService, new TestEditorServiceWithActiveEditor(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService(), quickInputService);
let service = new ConfigurationResolverService(new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService);
if (platform.isWindows) {
assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${workspaceFolder} ${env:key1} xyz'), 'abc foo \\VSCode\\workspaceLocation Value for key1 xyz');
} else {
@@ -189,7 +192,7 @@ suite('Configuration Resolver Service', () => {
}
});
let service = new ConfigurationResolverService(windowService, new TestEditorServiceWithActiveEditor(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService(), quickInputService);
let service = new ConfigurationResolverService(new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService);
if (platform.isWindows) {
assert.strictEqual(service.resolve(workspace, '${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} ${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), 'foo bar \\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation Value for key1 - Value for key2');
} else {
@@ -223,7 +226,7 @@ suite('Configuration Resolver Service', () => {
}
});
let service = new ConfigurationResolverService(windowService, new TestEditorServiceWithActiveEditor(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService(), quickInputService);
let service = new ConfigurationResolverService(new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService);
assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${config:editor.lineNumbers} ${config:editor.insertSpaces} xyz'), 'abc foo 123 false xyz');
});
@@ -233,7 +236,7 @@ suite('Configuration Resolver Service', () => {
editor: {}
});
let service = new ConfigurationResolverService(windowService, new TestEditorServiceWithActiveEditor(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService(), quickInputService);
let service = new ConfigurationResolverService(new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService);
assert.strictEqual(service.resolve(workspace, 'abc ${unknownVariable} xyz'), 'abc ${unknownVariable} xyz');
assert.strictEqual(service.resolve(workspace, 'abc ${env:unknownVariable} xyz'), 'abc xyz');
});
@@ -246,7 +249,7 @@ suite('Configuration Resolver Service', () => {
}
});
let service = new ConfigurationResolverService(windowService, new TestEditorServiceWithActiveEditor(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService(), quickInputService);
let service = new ConfigurationResolverService(new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService);
assert.throws(() => service.resolve(workspace, 'abc ${env} xyz'));
assert.throws(() => service.resolve(workspace, 'abc ${env:} xyz'));
@@ -626,13 +629,13 @@ class MockInputsConfigurationService extends TestConfigurationService {
}
}
class MockWindowService extends TestWindowService {
class MockWorkbenchEnvironmentService extends WorkbenchEnvironmentService {
constructor(private env: platform.IProcessEnvironment) {
super();
super(parseArgs(process.argv) as IWindowConfiguration, process.execPath);
}
getConfiguration(): IWindowConfiguration {
get configuration(): IWindowConfiguration {
return { userEnv: this.env } as IWindowConfiguration;
}
}

View File

@@ -8,7 +8,7 @@ import { IWindowService, INativeOpenDialogOptions, OpenDialogOptions, IURIToOpen
import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import * as resources from 'vs/base/common/resources';
@@ -28,7 +28,7 @@ export class FileDialogService implements IFileDialogService {
@IWindowService private readonly windowService: IWindowService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IHistoryService private readonly historyService: IHistoryService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IFileService private readonly fileService: IFileService
@@ -257,7 +257,7 @@ export class FileDialogService implements IFileDialogService {
}
private getSchemeFilterForWindow() {
return !this.windowService.getConfiguration().remoteAuthority ? Schemas.file : REMOTE_HOST_SCHEME;
return !this.environmentService.configuration.remoteAuthority ? Schemas.file : REMOTE_HOST_SCHEME;
}
private getFileSystemSchema(options: { availableFileSystems?: string[], defaultUri?: URI }): string {
@@ -266,7 +266,7 @@ export class FileDialogService implements IFileDialogService {
}
function isUntitledWorkspace(path: URI, environmentService: IEnvironmentService): boolean {
function isUntitledWorkspace(path: URI, environmentService: IWorkbenchEnvironmentService): boolean {
return resources.isEqualOrParent(path, environmentService.untitledWorkspacesHome);
}

View File

@@ -12,7 +12,6 @@ import { URI } from 'vs/base/common/uri';
import { isWindows } from 'vs/base/common/platform';
import { ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { ILabelService } from 'vs/platform/label/common/label';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { INotificationService } from 'vs/platform/notification/common/notification';
@@ -20,10 +19,11 @@ import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { Schemas } from 'vs/base/common/network';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { RemoteFileDialogContext } from 'vs/workbench/common/contextkeys';
import { equalsIgnoreCase } from 'vs/base/common/strings';
interface FileQuickPickItem extends IQuickPickItem {
uri: URI;
@@ -45,28 +45,29 @@ export class RemoteFileDialog {
private allowFolderSelection: boolean;
private remoteAuthority: string | undefined;
private requiresTrailing: boolean;
private userValue: string;
private scheme: string = REMOTE_HOST_SCHEME;
private shouldOverwriteFile: boolean = false;
private autoComplete: string;
private contextKey: IContextKey<boolean>;
private userEnteredPathSegment: string;
private autoCompletePathSegment: string;
private activeItem: FileQuickPickItem;
constructor(
@IFileService private readonly fileService: IFileService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IWindowService private readonly windowService: IWindowService,
@ILabelService private readonly labelService: ILabelService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@INotificationService private readonly notificationService: INotificationService,
@IFileDialogService private readonly fileDialogService: IFileDialogService,
@IModelService private readonly modelService: IModelService,
@IModeService private readonly modeService: IModeService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
@IContextKeyService contextKeyService: IContextKeyService
) {
this.remoteAuthority = this.windowService.getConfiguration().remoteAuthority;
this.remoteAuthority = this.environmentService.configuration.remoteAuthority;
this.contextKey = RemoteFileDialogContext.bindTo(contextKeyService);
}
@@ -134,7 +135,7 @@ export class RemoteFileDialog {
private remoteUriFrom(path: string): URI {
path = path.replace(/\\/g, '/');
return URI.from({ scheme: this.scheme, authority: this.remoteAuthority, path });
return resources.toLocalResource(URI.from({ scheme: this.scheme, path }), this.remoteAuthority);
}
private getScheme(defaultUri: URI | undefined, available: string[] | undefined): string {
@@ -179,7 +180,7 @@ export class RemoteFileDialog {
}
this.acceptButton = { iconPath: this.getDialogIcons('accept'), tooltip: this.options.title };
return new Promise<URI | undefined>((resolve) => {
return new Promise<URI | undefined>(async (resolve) => {
this.filePickBox = this.quickInputService.createQuickPick<FileQuickPickItem>();
this.filePickBox.matchOnLabel = false;
this.filePickBox.autoFocusOnList = false;
@@ -187,6 +188,8 @@ export class RemoteFileDialog {
let isResolving = false;
let isAcceptHandled = false;
this.currentFolder = homedir;
this.userEnteredPathSegment = '';
this.autoCompletePathSegment = '';
this.filePickBox.buttons = [this.acceptButton];
this.filePickBox.onDidTriggerButton(_ => {
// accept button
@@ -232,22 +235,25 @@ export class RemoteFileDialog {
this.filePickBox.onDidChangeActive(i => {
isAcceptHandled = false;
// update input box to match the first selected item
if (i.length === 1) {
this.setAutoComplete(this.userValue, resources.basename(this.remoteUriFrom(this.userValue)), i[0], true);
if ((i.length === 1) && this.isChangeFromUser()) {
this.setAutoComplete(this.constructFullUserPath(), this.userEnteredPathSegment, i[0], true);
}
});
this.filePickBox.onDidChangeValue(async value => {
// onDidChangeValue can also be triggered by the auto complete, so if it looks like the auto complete, don't do anything
if (!this.autoComplete || (value !== this.autoComplete)) {
if (value !== this.userValue) {
if (this.isChangeFromUser()) {
if (value !== this.constructFullUserPath()) {
this.filePickBox.validationMessage = undefined;
this.shouldOverwriteFile = false;
const valueUri = this.remoteUriFrom(this.trimTrailingSlash(this.filePickBox.value));
let isUpdate = false;
if (!resources.isEqual(this.remoteUriFrom(this.trimTrailingSlash(this.pathFromUri(this.currentFolder))), valueUri, true)) {
await this.tryUpdateItems(value, this.remoteUriFrom(this.filePickBox.value));
isUpdate = await this.tryUpdateItems(value, this.remoteUriFrom(this.filePickBox.value));
}
if (!isUpdate) {
this.setActiveItems(value);
}
this.setActiveItems(value);
} else {
this.filePickBox.activeItems = [];
}
@@ -262,16 +268,27 @@ export class RemoteFileDialog {
this.filePickBox.show();
this.contextKey.set(true);
this.updateItems(homedir, trailing);
await this.updateItems(homedir, trailing);
if (trailing) {
this.filePickBox.valueSelection = [this.filePickBox.value.length - trailing.length, this.filePickBox.value.length - ext.length];
} else {
this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length];
}
this.userValue = this.filePickBox.value;
});
}
private isChangeFromUser(): boolean {
if ((this.filePickBox.value === this.pathAppend(this.currentFolder, this.userEnteredPathSegment + this.autoCompletePathSegment))
&& (this.activeItem === (this.filePickBox.activeItems ? this.filePickBox.activeItems[0] : undefined))) {
return false;
}
return true;
}
private constructFullUserPath(): string {
return this.pathAppend(this.currentFolder, this.userEnteredPathSegment);
}
private async onDidAccept(): Promise<URI | undefined> {
// Check if Open Local has been selected
const selectedItems: ReadonlyArray<FileQuickPickItem> = this.filePickBox.selectedItems;
@@ -279,6 +296,7 @@ export class RemoteFileDialog {
if (this.options.availableFileSystems && (this.options.availableFileSystems.length > 1)) {
this.options.availableFileSystems.shift();
}
this.options.defaultUri = undefined;
if (this.requiresTrailing) {
return this.fileDialogService.showSaveDialog(this.options).then(result => {
return result;
@@ -331,14 +349,14 @@ export class RemoteFileDialog {
}
} else if (navigateValue) {
// Try to navigate into the folder
this.updateItems(navigateValue);
await this.updateItems(navigateValue);
} else {
// validation error. Path does not exist.
}
return Promise.resolve(undefined);
}
private async tryUpdateItems(value: string, valueUri: URI) {
private async tryUpdateItems(value: string, valueUri: URI): Promise<boolean> {
if (this.endsWithSlash(value) || (!resources.isEqual(this.currentFolder, resources.dirname(valueUri), true) && resources.isEqualOrParent(this.currentFolder, resources.dirname(valueUri), true))) {
let stat: IFileStat | undefined;
try {
@@ -346,11 +364,12 @@ export class RemoteFileDialog {
} catch (e) {
// do nothing
}
if (stat && stat.isDirectory && (resources.basename(valueUri) !== '.')) {
this.updateItems(valueUri);
if (stat && stat.isDirectory && (resources.basename(valueUri) !== '.') && this.endsWithSlash(value)) {
await this.updateItems(valueUri);
return true;
} else {
const inputUriDirname = resources.dirname(valueUri);
if (!resources.isEqual(this.currentFolder, inputUriDirname, true)) {
if (!resources.isEqual(this.remoteUriFrom(this.trimTrailingSlash(this.pathFromUri(this.currentFolder))), inputUriDirname, true)) {
let statWithoutTrailing: IFileStat | undefined;
try {
statWithoutTrailing = await this.fileService.resolve(inputUriDirname);
@@ -358,59 +377,70 @@ export class RemoteFileDialog {
// do nothing
}
if (statWithoutTrailing && statWithoutTrailing.isDirectory && (resources.basename(valueUri) !== '.')) {
this.updateItems(inputUriDirname, resources.basename(valueUri));
await this.updateItems(inputUriDirname, resources.basename(valueUri));
return true;
}
}
}
}
return false;
}
private setActiveItems(value: string) {
if (!this.userValue || (value !== this.userValue.substring(0, value.length))) {
const inputBasename = resources.basename(this.remoteUriFrom(value));
const inputBasename = resources.basename(this.remoteUriFrom(value));
// Make sure that the folder whose children we are currently viewing matches the path in the input
const userPath = this.constructFullUserPath();
if (userPath === value.substring(0, userPath.length)) {
let hasMatch = false;
for (let i = 0; i < this.filePickBox.items.length; i++) {
const item = <FileQuickPickItem>this.filePickBox.items[i];
if (this.setAutoComplete(value, inputBasename, item)) {
this.filePickBox.activeItems = [item];
hasMatch = true;
break;
}
}
if (!hasMatch) {
this.userEnteredPathSegment = inputBasename;
this.autoCompletePathSegment = '';
this.filePickBox.activeItems = [];
}
} else {
this.userValue = value;
this.autoComplete = '';
if (inputBasename !== resources.basename(this.currentFolder)) {
this.userEnteredPathSegment = inputBasename;
} else {
this.userEnteredPathSegment = '';
}
this.autoCompletePathSegment = '';
}
}
private setAutoComplete(startingValue: string, startingBasename: string, quickPickItem: FileQuickPickItem, force: boolean = false): boolean {
const itemBasename = (quickPickItem.label === '..') ? quickPickItem.label : resources.basename(quickPickItem.uri);
const itemPathLabel = (itemBasename === '..') ? this.pathAppend(this.currentFolder, itemBasename) : this.pathFromUri(quickPickItem.uri);
if (this.trimTrailingSlash(this.filePickBox.value) !== itemPathLabel) {
// Either foce the autocomplete, or the old value should be one smaller than the new value and match the new value.
if (!force && (itemBasename.length >= startingBasename.length) && (itemBasename.substr(0, startingBasename.length).toLowerCase() === startingBasename.toLowerCase())) {
this.userValue = startingValue;
const autoCompleteValue = itemBasename.substr(startingBasename.length);
this.autoComplete = startingValue + autoCompleteValue;
this.insertText(this.autoComplete, autoCompleteValue);
this.filePickBox.valueSelection = [startingValue.length, this.filePickBox.value.length];
return true;
} else if (force) {
this.userValue = this.pathFromUri(this.currentFolder, true);
this.autoComplete = this.pathAppend(this.currentFolder, itemBasename);
this.filePickBox.valueSelection = [this.userValue.length, this.filePickBox.value.length];
// use insert text to preserve undo buffer
this.insertText(this.autoComplete, itemBasename);
this.filePickBox.valueSelection = [this.filePickBox.value.length - itemBasename.length, this.filePickBox.value.length];
return true;
}
// Either force the autocomplete, or the old value should be one smaller than the new value and match the new value.
if (!force && (itemBasename.length >= startingBasename.length) && equalsIgnoreCase(itemBasename.substr(0, startingBasename.length), startingBasename)) {
this.userEnteredPathSegment = startingBasename;
this.activeItem = quickPickItem;
// Changing the active items will trigger the onDidActiveItemsChanged. Clear the autocomplete first, then set it after.
this.autoCompletePathSegment = '';
this.filePickBox.activeItems = [quickPickItem];
this.autoCompletePathSegment = itemBasename.substr(startingBasename.length);
this.insertText(startingValue + this.autoCompletePathSegment, this.autoCompletePathSegment);
this.filePickBox.valueSelection = [startingValue.length, this.filePickBox.value.length];
return true;
} else if (force && (quickPickItem.label !== (this.userEnteredPathSegment + this.autoCompletePathSegment))) {
this.userEnteredPathSegment = '';
this.autoCompletePathSegment = itemBasename;
this.activeItem = quickPickItem;
this.filePickBox.valueSelection = [this.pathFromUri(this.currentFolder, true).length, this.filePickBox.value.length];
// use insert text to preserve undo buffer
this.insertText(this.pathAppend(this.currentFolder, itemBasename), itemBasename);
this.filePickBox.valueSelection = [this.filePickBox.value.length - itemBasename.length, this.filePickBox.value.length];
return true;
} else {
this.userEnteredPathSegment = startingBasename;
this.autoCompletePathSegment = '';
return false;
}
this.userValue = startingValue;
this.autoComplete = '';
return false;
}
private insertText(wholeValue: string, insertText: string) {
@@ -528,15 +558,15 @@ export class RemoteFileDialog {
return Promise.resolve(true);
}
private updateItems(newFolder: URI, trailing?: string) {
this.currentFolder = newFolder;
this.userValue = this.pathFromUri(newFolder);
this.autoComplete = '';
private async updateItems(newFolder: URI, trailing?: string) {
this.userEnteredPathSegment = trailing ? trailing : '';
this.autoCompletePathSegment = '';
this.filePickBox.valueSelection = [0, this.filePickBox.value.length];
const newValue = trailing ? this.pathFromUri(resources.joinPath(newFolder, trailing)) : this.pathFromUri(newFolder, true);
this.currentFolder = this.remoteUriFrom(this.pathFromUri(newFolder, true));
this.insertText(newValue, newValue);
this.filePickBox.busy = true;
this.createItems(this.currentFolder).then(items => {
return this.createItems(this.currentFolder).then(items => {
this.filePickBox.items = items;
if (this.allowFolderSelection) {
this.filePickBox.activeItems = [];
@@ -560,8 +590,9 @@ export class RemoteFileDialog {
}
private pathAppend(uri: URI, additional: string): string {
if (additional === '..') {
return this.pathFromUri(uri) + this.labelService.getSeparator(uri.scheme, uri.authority) + additional;
if ((additional === '..') || (additional === '.')) {
const basePath = this.pathFromUri(uri);
return basePath + (this.endsWithSlash(basePath) ? '' : this.labelService.getSeparator(uri.scheme, uri.authority)) + additional;
} else {
return this.pathFromUri(resources.joinPath(uri, additional));
}

View File

@@ -207,6 +207,11 @@ export interface IEditorGroupsService {
*/
readonly whenRestored: Promise<void>;
/**
* Find out if the editor group service has editors to restore from a previous session.
*/
readonly willRestoreEditors: boolean;
/**
* Get all groups that are currently visible in the editor area optionally
* sorted by being most recent active or grid order. Will sort by creation

View File

@@ -0,0 +1,16 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
export const IWorkbenchEnvironmentService = createDecorator<IWorkbenchEnvironmentService>('environmentService');
export interface IWorkbenchEnvironmentService extends IEnvironmentService {
_serviceBrand: any;
readonly configuration: IWindowConfiguration;
}

View File

@@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
export class WorkbenchEnvironmentService extends EnvironmentService implements IWorkbenchEnvironmentService {
_serviceBrand: any;
constructor(
private _configuration: IWindowConfiguration,
execPath: string
) {
super(_configuration, execPath);
}
get configuration(): IWindowConfiguration {
return this._configuration;
}
}

View File

@@ -10,11 +10,10 @@ import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnab
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { ExtensionType, IExtension } from 'vs/platform/extensions/common/extensions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
@@ -33,9 +32,8 @@ export class ExtensionEnablementService extends Disposable implements IExtension
constructor(
@IStorageService storageService: IStorageService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IWindowService private readonly windowService: IWindowService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
) {
@@ -138,7 +136,7 @@ export class ExtensionEnablementService extends Disposable implements IExtension
if (Array.isArray(disabledExtensions)) {
return disabledExtensions.some(id => areSameExtensions({ id }, extension.identifier));
}
if (this.windowService.getConfiguration().remoteAuthority) {
if (this.environmentService.configuration.remoteAuthority) {
const server = isUIExtension(extension.manifest, this.configurationService) ? this.extensionManagementServerService.localExtensionManagementServer : this.extensionManagementServerService.remoteExtensionManagementServer;
return this.extensionManagementServerService.getExtensionManagementServer(extension.location) !== server;
}

View File

@@ -9,14 +9,12 @@ import { ExtensionEnablementService } from 'vs/workbench/services/extensionManag
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { Emitter } from 'vs/base/common/event';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IStorageService, InMemoryStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IExtensionContributions, ExtensionType, IExtension } from 'vs/platform/extensions/common/extensions';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { TestWindowService } from 'vs/workbench/test/workbenchTestServices';
function storageService(instantiationService: TestInstantiationService): IStorageService {
let service = instantiationService.get(IStorageService);
@@ -34,11 +32,13 @@ function storageService(instantiationService: TestInstantiationService): IStorag
export class TestExtensionEnablementService extends ExtensionEnablementService {
constructor(instantiationService: TestInstantiationService) {
super(storageService(instantiationService), instantiationService.get(IWorkspaceContextService),
instantiationService.get(IEnvironmentService) || instantiationService.stub(IEnvironmentService, {} as IEnvironmentService),
super(
storageService(instantiationService),
instantiationService.get(IWorkspaceContextService),
instantiationService.get(IWorkbenchEnvironmentService) || instantiationService.stub(IWorkbenchEnvironmentService, { configuration: Object.create(null) } as IWorkbenchEnvironmentService),
instantiationService.get(IExtensionManagementService) || instantiationService.stub(IExtensionManagementService,
{ onDidInstallExtension: new Emitter<DidInstallExtensionEvent>().event, onDidUninstallExtension: new Emitter<DidUninstallExtensionEvent>().event } as IExtensionManagementService),
instantiationService.stub(IWindowService, new TestWindowService()), instantiationService.get(IConfigurationService), instantiationService.get(IExtensionManagementServerService));
instantiationService.get(IConfigurationService), instantiationService.get(IExtensionManagementServerService));
}
public reset(): void {
@@ -437,26 +437,26 @@ suite('ExtensionEnablementService Test', () => {
});
test('test canChangeEnablement return false when extensions are disabled in environment', () => {
instantiationService.stub(IEnvironmentService, { disableExtensions: true } as IEnvironmentService);
instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true } as IWorkbenchEnvironmentService);
testObject = new TestExtensionEnablementService(instantiationService);
assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a')), false);
});
test('test canChangeEnablement return false when the extension is disabled in environment', () => {
instantiationService.stub(IEnvironmentService, { disableExtensions: ['pub.a'] } as IEnvironmentService);
instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService);
testObject = new TestExtensionEnablementService(instantiationService);
assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a')), false);
});
test('test canChangeEnablement return true for system extensions when extensions are disabled in environment', () => {
instantiationService.stub(IEnvironmentService, { disableExtensions: true } as IEnvironmentService);
instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true } as IWorkbenchEnvironmentService);
testObject = new TestExtensionEnablementService(instantiationService);
const extension = aLocalExtension('pub.a', undefined, ExtensionType.System);
assert.equal(testObject.canChangeEnablement(extension), true);
});
test('test canChangeEnablement return false for system extension when extension is disabled in environment', () => {
instantiationService.stub(IEnvironmentService, { disableExtensions: ['pub.a'] } as IEnvironmentService);
instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService);
testObject = new TestExtensionEnablementService(instantiationService);
const extension = aLocalExtension('pub.a', undefined, ExtensionType.System);
assert.ok(!testObject.canChangeEnablement(extension));
@@ -464,7 +464,7 @@ suite('ExtensionEnablementService Test', () => {
test('test extension is disabled when disabled in enviroment', async () => {
const extension = aLocalExtension('pub.a');
instantiationService.stub(IEnvironmentService, { disableExtensions: ['pub.a'] } as IEnvironmentService);
instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService);
instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: didUninstallEvent.event, onDidInstallExtension: didInstallEvent.event, getInstalled: () => Promise.resolve([extension, aLocalExtension('pub.b')]) } as IExtensionManagementService);
testObject = new TestExtensionEnablementService(instantiationService);
assert.ok(!testObject.isEnabled(extension));

View File

@@ -18,15 +18,13 @@ export function parseExtensionDevOptions(environmentService: IEnvironmentService
let isExtensionDevHost = environmentService.isExtensionDevelopment;
let debugOk = true;
let extDevLoc = environmentService.extensionDevelopmentLocationURI;
if (Array.isArray(extDevLoc)) {
for (let x of extDevLoc) {
let extDevLocs = environmentService.extensionDevelopmentLocationURI;
if (extDevLocs) {
for (let x of extDevLocs) {
if (x.scheme !== Schemas.file) {
debugOk = false;
}
}
} else if (extDevLoc && extDevLoc.scheme !== Schemas.file) {
debugOk = false;
}
let isExtensionDevDebug = debugOk && typeof environmentService.debugExtensionHost.port === 'number';

View File

@@ -10,6 +10,22 @@ import { IRemoteConsoleLog } from 'vs/base/common/console';
export const IExtensionHostDebugService = createDecorator<IExtensionHostDebugService>('extensionHostDebugService');
export interface IAttachSessionEvent {
sessionId: string;
subId?: string;
port: number;
}
export interface ILogToSessionEvent {
sessionId: string;
log: IRemoteConsoleLog;
}
export interface ITerminateSessionEvent {
sessionId: string;
subId?: string;
}
export interface IExtensionHostDebugService {
_serviceBrand: any;
@@ -19,12 +35,12 @@ export interface IExtensionHostDebugService {
close(resource: URI): void;
onClose: Event<URI>;
attachSession(id: string, port: number): void;
onAttachSession: Event<{ id: string, port: number }>;
attachSession(sessionId: string, port: number, subId?: string): void;
onAttachSession: Event<IAttachSessionEvent>;
logToSession(id: string, log: IRemoteConsoleLog): void;
onLogToSession: Event<{ id: string, log: IRemoteConsoleLog }>;
logToSession(sessionId: string, log: IRemoteConsoleLog): void;
onLogToSession: Event<ILogToSessionEvent>;
terminateSession(id: string): void;
onTerminateSession: Event<string>;
terminateSession(sessionId: string, subId?: string): void;
onTerminateSession: Event<ITerminateSessionEvent>;
}

View File

@@ -11,7 +11,7 @@ import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { IURITransformer, transformIncomingURIs } from 'vs/base/common/uriIpc';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { LazyPromise } from 'vs/workbench/services/extensions/node/lazyPromise';
import { LazyPromise } from 'vs/workbench/services/extensions/common/lazyPromise';
import { IRPCProtocol, ProxyIdentifier, getStringIdentifierForProxy } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import { VSBuffer } from 'vs/base/common/buffer';
@@ -739,7 +739,6 @@ class MessageIO {
}
private static _serializeReplyOKVSBuffer(req: number, res: VSBuffer): VSBuffer {
let len = 0;
len += MessageBuffer.sizeVSBuffer(res);
@@ -809,10 +808,10 @@ const enum MessageType {
Cancel = 6,
ReplyOKEmpty = 7,
ReplyOKBuffer = 8,
ReplyOKVSBuffer = 8,
ReplyOKJSON = 9,
ReplyErrError = 10,
ReplyErrEmpty = 11,
ReplyOKVSBuffer = 9,
ReplyOKJSON = 10,
ReplyErrError = 11,
ReplyErrEmpty = 12,
}
const enum ArgType {

View File

@@ -294,30 +294,19 @@ export class CachedExtensionScanner {
// Always load developed extensions while extensions development
let developedExtensions: Promise<IExtensionDescription[]> = Promise.resolve([]);
if (environmentService.isExtensionDevelopment) {
if (Array.isArray(environmentService.extensionDevelopmentLocationURI)) {
const extDescsP = environmentService.extensionDevelopmentLocationURI.filter(extLoc => extLoc.scheme === Schemas.file).map(extLoc => {
return ExtensionScanner.scanOneOrMultipleExtensions(
new ExtensionScannerInput(version, commit, locale, devMode, originalFSPath(extLoc), false, true, translations), log
);
});
developedExtensions = Promise.all(extDescsP).then((extDescArrays: IExtensionDescription[][]) => {
let extDesc: IExtensionDescription[] = [];
for (let eds of extDescArrays) {
extDesc = extDesc.concat(eds);
}
return extDesc;
});
} else if (environmentService.extensionDevelopmentLocationURI) {
if (environmentService.extensionDevelopmentLocationURI.scheme === Schemas.file) {
developedExtensions = ExtensionScanner.scanOneOrMultipleExtensions(
new ExtensionScannerInput(version, commit, locale, devMode, originalFSPath(environmentService.extensionDevelopmentLocationURI), false, true, translations), log
);
if (environmentService.isExtensionDevelopment && environmentService.extensionDevelopmentLocationURI) {
const extDescsP = environmentService.extensionDevelopmentLocationURI.filter(extLoc => extLoc.scheme === Schemas.file).map(extLoc => {
return ExtensionScanner.scanOneOrMultipleExtensions(
new ExtensionScannerInput(version, commit, locale, devMode, originalFSPath(extLoc), false, true, translations), log
);
});
developedExtensions = Promise.all(extDescsP).then((extDescArrays: IExtensionDescription[][]) => {
let extDesc: IExtensionDescription[] = [];
for (let eds of extDescArrays) {
extDesc = extDesc.concat(eds);
}
}
return extDesc;
});
}
return Promise.all([finalBuiltinExtensions, userExtensions, developedExtensions]).then((extensionDescriptions: IExtensionDescription[][]) => {

View File

@@ -22,7 +22,7 @@ import { findFreePort, randomPort } from 'vs/base/node/ports';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net';
import { generateRandomPipeName, NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { ILabelService } from 'vs/platform/label/common/label';
import { ILifecycleService, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
@@ -32,7 +32,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IInitData } from 'vs/workbench/api/common/extHost.protocol';
import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/node/extensionHostProtocol';
import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { parseExtensionDevOptions } from '../common/extensionDevOptions';
@@ -78,7 +78,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
@IWindowsService private readonly _windowsService: IWindowsService,
@IWindowService private readonly _windowService: IWindowService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@ILogService private readonly _logService: ILogService,
@ILabelService private readonly _labelService: ILabelService,
@@ -124,10 +124,8 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
private matchesExtDevLocations(resource: URI): boolean {
const extDevLocs = this._environmentService.extensionDevelopmentLocationURI;
if (Array.isArray(extDevLocs)) {
if (extDevLocs) {
return extDevLocs.some(extDevLoc => isEqual(extDevLoc, resource));
} else if (extDevLocs) {
return isEqual(extDevLocs, resource);
}
return false;
}
@@ -240,7 +238,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
// Help in case we fail to start it
let startupTimeoutHandle: any;
if (!this._environmentService.isBuilt && !this._windowService.getConfiguration().remoteAuthority || this._isExtensionDevHost) {
if (!this._environmentService.isBuilt && !this._environmentService.configuration.remoteAuthority || this._isExtensionDevHost) {
startupTimeoutHandle = setTimeout(() => {
const msg = this._isExtensionDevDebugBrk
? nls.localize('extensionHostProcess.startupFailDebug', "Extension host did not start in 10 seconds, it might be stopped on the first line and needs a debugger to continue.")

View File

@@ -6,7 +6,7 @@
import { Event, Emitter } from 'vs/base/common/event';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IExtensionHostDebugService } from 'vs/workbench/services/extensions/common/extensionHostDebug';
import { IExtensionHostDebugService, IAttachSessionEvent, ITerminateSessionEvent, ILogToSessionEvent } from 'vs/workbench/services/extensions/common/extensionHostDebug';
import { URI } from 'vs/base/common/uri';
import { IRemoteConsoleLog } from 'vs/base/common/console';
import { ipcRenderer as ipc } from 'electron';
@@ -16,10 +16,8 @@ interface IReloadBroadcast {
resource: string;
}
interface IAttachSessionBroadcast {
interface IAttachSessionBroadcast extends IAttachSessionEvent {
type: 'vscode:extensionAttach';
id: string;
port: number;
}
interface ICloseBroadcast {
@@ -27,15 +25,12 @@ interface ICloseBroadcast {
resource: string;
}
interface ILogToSessionBroadcast {
interface ILogToSessionBroadcast extends ILogToSessionEvent {
type: 'vscode:extensionLog';
id: string;
log: IRemoteConsoleLog;
}
interface ITerminateSessionBroadcast {
interface ITerminateSessionBroadcast extends ITerminateSessionEvent {
type: 'vscode:extensionTerminate';
id: string;
}
const CHANNEL = 'vscode:extensionHostDebug';
@@ -46,30 +41,32 @@ class ExtensionHostDebugService implements IExtensionHostDebugService {
private windowId: number;
private readonly _onReload = new Emitter<URI>();
private readonly _onClose = new Emitter<URI>();
private readonly _onAttachSession = new Emitter<{ id: string, port: number }>();
private readonly _onLogToSession = new Emitter<{ id: string, log: IRemoteConsoleLog }>();
private readonly _onTerminateSession = new Emitter<string>();
private readonly _onAttachSession = new Emitter<IAttachSessionEvent>();
private readonly _onLogToSession = new Emitter<ILogToSessionEvent>();
private readonly _onTerminateSession = new Emitter<ITerminateSessionEvent>();
constructor(
@IWindowService readonly windowService: IWindowService,
) {
this.windowId = windowService.getCurrentWindowId();
this.windowId = windowService.windowId;
ipc.on(CHANNEL, (_: unknown, broadcast: IReloadBroadcast | ICloseBroadcast | IAttachSessionBroadcast | ILogToSessionBroadcast | ITerminateSessionBroadcast) => {
if (broadcast.type === 'vscode:extensionReload') {
this._onReload.fire(URI.parse(broadcast.resource));
}
if (broadcast.type === 'vscode:extensionCloseExtensionHost') {
this._onClose.fire(URI.parse(broadcast.resource));
}
if (broadcast.type === 'vscode:extensionAttach') {
this._onAttachSession.fire({ id: broadcast.id, port: broadcast.port });
}
if (broadcast.type === 'vscode:extensionLog') {
this._onLogToSession.fire({ id: broadcast.id, log: broadcast.log });
}
if (broadcast.type === 'vscode:extensionTerminate') {
this._onTerminateSession.fire(broadcast.id);
switch (broadcast.type) {
case 'vscode:extensionReload':
this._onReload.fire(URI.parse(broadcast.resource));
break;
case 'vscode:extensionCloseExtensionHost':
this._onClose.fire(URI.parse(broadcast.resource));
break;
case 'vscode:extensionAttach':
this._onAttachSession.fire(broadcast);
break;
case 'vscode:extensionLog':
this._onLogToSession.fire(broadcast);
break;
case 'vscode:extensionTerminate':
this._onTerminateSession.fire(broadcast);
break;
}
});
}
@@ -96,38 +93,40 @@ class ExtensionHostDebugService implements IExtensionHostDebugService {
return this._onClose.event;
}
attachSession(id: string, port: number): void {
attachSession(sessionId: string, port: number, subId?: string): void {
ipc.send(CHANNEL, this.windowId, <IAttachSessionBroadcast>{
type: 'vscode:extensionAttach',
id,
port
sessionId,
port,
subId
});
}
get onAttachSession(): Event<{ id: string, port: number }> {
get onAttachSession(): Event<IAttachSessionEvent> {
return this._onAttachSession.event;
}
logToSession(id: string, log: IRemoteConsoleLog): void {
logToSession(sessionId: string, log: IRemoteConsoleLog): void {
ipc.send(CHANNEL, this.windowId, <ILogToSessionBroadcast>{
type: 'vscode:extensionLog',
id,
sessionId,
log
});
}
get onLogToSession(): Event<{ id: string, log: IRemoteConsoleLog }> {
get onLogToSession(): Event<ILogToSessionEvent> {
return this._onLogToSession.event;
}
terminateSession(id: string): void {
terminateSession(sessionId: string, subId?: string): void {
ipc.send(CHANNEL, this.windowId, <ITerminateSessionBroadcast>{
type: 'vscode:extensionTerminate',
id
sessionId,
subId
});
}
get onTerminateSession(): Event<string> {
get onTerminateSession(): Event<ITerminateSessionEvent> {
return this._onTerminateSession.event;
}
}

View File

@@ -16,7 +16,7 @@ import { ProfileSession } from 'vs/workbench/services/extensions/common/extensio
import { IExtensionHostStarter } from 'vs/workbench/services/extensions/electron-browser/extensionHost';
import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-browser/extensionHostProfiler';
import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import { IRPCProtocolLogger, RPCProtocol, RequestInitiator, ResponsiveState } from 'vs/workbench/services/extensions/node/rpcProtocol';
import { IRPCProtocolLogger, RPCProtocol, RequestInitiator, ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol';
import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import * as nls from 'vs/nls';
@@ -27,6 +27,7 @@ import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/wor
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IUntitledResourceInput } from 'vs/workbench/common/editor';
import { StopWatch } from 'vs/base/common/stopwatch';
import { VSBuffer } from 'vs/base/common/buffer';
// Enable to see detailed message communication between window and extension host
const LOG_EXTENSION_HOST_COMMUNICATION = false;
@@ -154,7 +155,7 @@ export class ExtensionHostProcessManager extends Disposable {
let b = Buffer.alloc(SIZE, Math.random() % 256);
const sw = StopWatch.create(true);
await proxy.$test_up(b);
await proxy.$test_up(VSBuffer.wrap(b));
sw.stop();
return ExtensionHostProcessManager._convert(SIZE, sw.elapsed());
}

View File

@@ -33,7 +33,7 @@ export class ExtensionManagementServerService implements IExtensionManagementSer
const remoteAgentConnection = remoteAgentService.getConnection();
if (remoteAgentConnection) {
const extensionManagementService = new ExtensionManagementChannelClient(remoteAgentConnection.getChannel<IChannel>('extensions'));
this.remoteExtensionManagementServer = { authority: remoteAgentConnection.remoteAuthority, extensionManagementService, label: remoteAgentConnection.remoteAuthority };
this.remoteExtensionManagementServer = { authority: remoteAgentConnection.remoteAuthority, extensionManagementService, label: localize('remote', "Remote") };
}
}

View File

@@ -13,7 +13,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import * as perf from 'vs/base/common/performance';
import { isEqualOrParent } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { EnablementState, IExtensionEnablementService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { BetterMergeId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -26,8 +26,8 @@ import { IWindowService, IWindowsService } from 'vs/platform/windows/common/wind
import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, ProfileSession, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser, schema } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ExtensionHostProcessWorker } from 'vs/workbench/services/extensions/electron-browser/extensionHost';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry';
import { ResponsiveState } from 'vs/workbench/services/extensions/node/rpcProtocol';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol';
import { CachedExtensionScanner, Logger } from 'vs/workbench/services/extensions/electron-browser/cachedExtensionScanner';
import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/electron-browser/extensionHostProcessManager';
import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
@@ -97,7 +97,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@INotificationService private readonly _notificationService: INotificationService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@IExtensionEnablementService private readonly _extensionEnablementService: IExtensionEnablementService,
@IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService,
@@ -112,7 +112,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
e.join(this.activateByEvent(`onFileSystem:${e.scheme}`));
}));
this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${this._windowService.getCurrentWindowId()}`));
this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${this._windowService.windowId}`));
this._registry = new ExtensionDescriptionRegistry([]);
this._installedExtensionsReady = new Barrier();
this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment;
@@ -189,7 +189,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
private async _deltaExtensions(_toAdd: IExtension[], _toRemove: string[]): Promise<void> {
if (this._windowService.getConfiguration().remoteAuthority) {
if (this._environmentService.configuration.remoteAuthority) {
return;
}
@@ -283,7 +283,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
public canAddExtension(extension: IExtensionDescription): boolean {
if (this._windowService.getConfiguration().remoteAuthority) {
if (this._environmentService.configuration.remoteAuthority) {
return false;
}
@@ -303,7 +303,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
public canRemoveExtension(extension: IExtensionDescription): boolean {
if (this._windowService.getConfiguration().remoteAuthority) {
if (this._environmentService.configuration.remoteAuthority) {
return false;
}
@@ -647,16 +647,14 @@ export class ExtensionService extends Disposable implements IExtensionService {
private isExtensionUnderDevelopment(extension: IExtensionDescription): boolean {
if (this._environmentService.isExtensionDevelopment) {
const extDevLoc = this._environmentService.extensionDevelopmentLocationURI;
const extLocation = extension.extensionLocation;
if (Array.isArray(extDevLoc)) {
for (let p of extDevLoc) {
const extDevLocs = this._environmentService.extensionDevelopmentLocationURI;
if (extDevLocs) {
const extLocation = extension.extensionLocation;
for (let p of extDevLocs) {
if (isEqualOrParent(extLocation, p)) {
return true;
}
}
} else if (extDevLoc) {
return isEqualOrParent(extLocation, extDevLoc);
}
}
return false;

View File

@@ -10,67 +10,56 @@ import { Counter } from 'vs/base/common/numbers';
import { URI, setUriThrowOnMissingScheme } from 'vs/base/common/uri';
import { IURITransformer } from 'vs/base/common/uriIpc';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { IEnvironment, IInitData, MainContext, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
import { IInitData, MainContext, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration';
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol';
import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService';
import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { RPCProtocol } from 'vs/workbench/services/extensions/common/rpcProtocol';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { withNullAsUndefined } from 'vs/base/common/types';
import { ILogService } from 'vs/platform/log/common/log';
// we don't (yet) throw when extensions parse
// uris that have no scheme
setUriThrowOnMissingScheme(false);
const nativeExit = process.exit.bind(process);
function patchProcess(allowExit: boolean) {
process.exit = function (code?: number) {
if (allowExit) {
exit(code);
} else {
const err = new Error('An extension called process.exit() and this was prevented.');
console.warn(err.stack);
}
} as (code?: number) => never;
process.crash = function () {
const err = new Error('An extension called process.crash() and this was prevented.');
console.warn(err.stack);
};
export interface IExitFn {
(code?: number): any;
}
export function exit(code?: number) {
nativeExit(code);
export interface IConsolePatchFn {
(mainThreadConsole: MainThreadConsoleShape): any;
}
export interface ILogServiceFn {
(initData: IInitData): ILogService;
}
export class ExtensionHostMain {
private _isTerminating: boolean;
private readonly _environment: IEnvironment;
private readonly _exitFn: IExitFn;
private readonly _extensionService: ExtHostExtensionService;
private readonly _extHostLogService: ExtHostLogService;
private disposables: IDisposable[] = [];
private _searchRequestIdProvider: Counter;
constructor(protocol: IMessagePassingProtocol, initData: IInitData) {
constructor(protocol: IMessagePassingProtocol, initData: IInitData, exitFn: IExitFn, consolePatchFn: IConsolePatchFn, logServiceFn: ILogServiceFn) {
this._isTerminating = false;
this._exitFn = exitFn;
const uriTransformer: IURITransformer | null = null;
const rpcProtocol = new RPCProtocol(protocol, null, uriTransformer);
// ensure URIs are transformed and revived
initData = this.transform(initData, rpcProtocol);
this._environment = initData.environment;
const allowExit = !!this._environment.extensionTestsLocationURI; // to support other test frameworks like Jasmin that use process.exit (https://github.com/Microsoft/vscode/issues/37708)
patchProcess(allowExit);
this._patchPatchedConsole(rpcProtocol.getProxy(MainContext.MainThreadConsole));
// allow to patch console
consolePatchFn(rpcProtocol.getProxy(MainContext.MainThreadConsole));
// services
this._extHostLogService = new ExtHostLogService(initData.logLevel, initData.logsLocation.fsPath);
this._extHostLogService = new ExtHostLogService(logServiceFn(initData), initData.logsLocation.fsPath);
this.disposables.push(this._extHostLogService);
this._searchRequestIdProvider = new Counter();
@@ -80,7 +69,7 @@ export class ExtensionHostMain {
this._extHostLogService.trace('initData', initData);
const extHostConfiguraiton = new ExtHostConfiguration(rpcProtocol.getProxy(MainContext.MainThreadConfiguration), extHostWorkspace);
this._extensionService = new ExtHostExtensionService(nativeExit, initData, rpcProtocol, extHostWorkspace, extHostConfiguraiton, this._extHostLogService);
this._extensionService = new ExtHostExtensionService(exitFn, initData, rpcProtocol, extHostWorkspace, extHostConfiguraiton, initData.environment, this._extHostLogService);
// error forwarding and stack trace scanning
Error.stackTraceLimit = 100; // increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API)
@@ -116,18 +105,6 @@ export class ExtensionHostMain {
});
}
private _patchPatchedConsole(mainThreadConsole: MainThreadConsoleShape): void {
// The console is already patched to use `process.send()`
const nativeProcessSend = process.send!;
process.send = (...args: any[]) => {
if (args.length === 0 || !args[0] || args[0].type !== '__$console') {
return nativeProcessSend.apply(process, args);
}
mainThreadConsole.$logExtensionHostMessage(args[0]);
};
}
terminate(): void {
if (this._isTerminating) {
// we are already shutting down...
@@ -145,7 +122,7 @@ export class ExtensionHostMain {
// Give extensions 1 second to wrap up any async dispose, then exit in at most 4 seconds
setTimeout(() => {
Promise.race([timeout(4000), extensionsDeactivated]).then(() => exit(), () => exit());
Promise.race([timeout(4000), extensionsDeactivated]).finally(() => this._exitFn());
}, 1000);
}
@@ -153,7 +130,10 @@ export class ExtensionHostMain {
initData.extensions.forEach((ext) => (<any>ext).extensionLocation = URI.revive(rpcProtocol.transformIncomingURIs(ext.extensionLocation)));
initData.environment.appRoot = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appRoot));
initData.environment.appSettingsHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appSettingsHome));
initData.environment.extensionDevelopmentLocationURI = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.extensionDevelopmentLocationURI));
const extDevLocs = initData.environment.extensionDevelopmentLocationURI;
if (extDevLocs) {
initData.environment.extensionDevelopmentLocationURI = extDevLocs.map(url => URI.revive(rpcProtocol.transformIncomingURIs(url)));
}
initData.environment.extensionTestsLocationURI = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.extensionTestsLocationURI));
initData.environment.globalStorageHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.globalStorageHome));
initData.environment.userHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.userHome));

View File

@@ -11,10 +11,12 @@ import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { PersistentProtocol, ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net';
import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import product from 'vs/platform/product/node/product';
import { IInitData } from 'vs/workbench/api/common/extHost.protocol';
import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage } from 'vs/workbench/services/extensions/node/extensionHostProtocol';
import { exit, ExtensionHostMain } from 'vs/workbench/services/extensions/node/extensionHostMain';
import { IInitData, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol';
import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { ExtensionHostMain, IExitFn, ILogServiceFn } from 'vs/workbench/services/extensions/node/extensionHostMain';
import { VSBuffer } from 'vs/base/common/buffer';
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions';
// With Electron 2.x and node.js 8.x the "natives" module
// can cause a native crash (see https://github.com/nodejs/node/issues/19891 and
@@ -34,6 +36,39 @@ import { VSBuffer } from 'vs/base/common/buffer';
};
})();
// custom process.exit logic...
const nativeExit: IExitFn = process.exit.bind(process);
function patchProcess(allowExit: boolean) {
process.exit = function (code?: number) {
if (allowExit) {
nativeExit(code);
} else {
const err = new Error('An extension called process.exit() and this was prevented.');
console.warn(err.stack);
}
} as (code?: number) => never;
process.crash = function () {
const err = new Error('An extension called process.crash() and this was prevented.');
console.warn(err.stack);
};
}
// use IPC messages to forward console-calls
function patchPatchedConsole(mainThreadConsole: MainThreadConsoleShape): void {
// The console is already patched to use `process.send()`
const nativeProcessSend = process.send!;
process.send = (...args: any[]) => {
if (args.length === 0 || !args[0] || args[0].type !== '__$console') {
return nativeProcessSend.apply(process, args);
}
mainThreadConsole.$logExtensionHostMessage(args[0]);
};
}
const createLogService: ILogServiceFn = initData => createSpdLogService(ExtensionHostLogFileName, initData.logLevel, initData.logsLocation.fsPath);
interface IRendererConnection {
protocol: IMessagePassingProtocol;
initData: IInitData;
@@ -42,7 +77,7 @@ interface IRendererConnection {
// This calls exit directly in case the initialization is not finished and we need to exit
// Otherwise, if initialization completed we go to extensionHostMain.terminate()
let onTerminate = function () {
exit();
nativeExit();
};
function _createExtHostProtocol(): Promise<IMessagePassingProtocol> {
@@ -149,7 +184,7 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRenderer
if (rendererCommit && myCommit) {
// Running in the built version where commits are defined
if (rendererCommit !== myCommit) {
exit(55);
nativeExit(55);
}
}
@@ -230,8 +265,13 @@ createExtHostProtocol().then(protocol => {
// connect to main side
return connectToRenderer(protocol);
}).then(renderer => {
const { initData } = renderer;
// setup things
const extensionHostMain = new ExtensionHostMain(renderer.protocol, renderer.initData);
patchProcess(!!initData.environment.extensionTestsLocationURI); // to support other test frameworks like Jasmin that use process.exit (https://github.com/Microsoft/vscode/issues/37708)
const extensionHostMain = new ExtensionHostMain(renderer.protocol, initData, nativeExit, patchPatchedConsole, createLogService);
// rewrite onTerminate-function to be a proper shutdown
onTerminate = () => extensionHostMain.terminate();
}).catch(err => console.error(err));

View File

@@ -13,11 +13,11 @@ import * as cp from 'child_process';
import { assign } from 'vs/base/common/objects';
import { endsWith } from 'vs/base/common/strings';
import { IExtHostWorkspaceProvider } from 'vs/workbench/api/node/extHostWorkspace';
import { ExtHostConfigProvider } from 'vs/workbench/api/node/extHostConfiguration';
import { IExtHostWorkspaceProvider } from 'vs/workbench/api/common/extHostWorkspace';
import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration';
import { ProxyAgent } from 'vscode-proxy-agent';
import { MainThreadTelemetryShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
import { URI } from 'vs/base/common/uri';

View File

@@ -8,7 +8,7 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance
import { Emitter, Event } from 'vs/base/common/event';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol';
import { RPCProtocol } from 'vs/workbench/services/extensions/common/rpcProtocol';
import { VSBuffer } from 'vs/base/common/buffer';
suite('RPCProtocol', () => {

View File

@@ -7,7 +7,7 @@ import * as paths from 'vs/base/common/path';
import * as fs from 'fs';
import * as os from 'os';
import * as assert from 'assert';
import { FileOperation, FileOperationEvent, IContent, IResolveContentOptions, IFileStat, IStreamContent, FileOperationError, FileOperationResult, IUpdateContentOptions, ICreateFileOptions, IContentData, ITextSnapshot, ILegacyFileService, IFileStatWithMetadata, IFileService, IFileSystemProvider, etag } from 'vs/platform/files/common/files';
import { FileOperation, FileOperationEvent, IContent, IResolveContentOptions, IFileStat, IStreamContent, FileOperationError, FileOperationResult, IWriteTextFileOptions, ICreateFileOptions, IContentData, ITextSnapshot, ILegacyFileService, IFileStatWithMetadata, IFileService, IFileSystemProvider, etag } from 'vs/platform/files/common/files';
import { MAX_FILE_SIZE, MAX_HEAP_SIZE } from 'vs/platform/files/node/fileConstants';
import * as objects from 'vs/base/common/objects';
import { timeout } from 'vs/base/common/async';
@@ -382,7 +382,7 @@ export class LegacyFileService extends Disposable implements ILegacyFileService
//#region File Writing
updateContent(resource: uri, value: string | ITextSnapshot, options: IUpdateContentOptions = Object.create(null)): Promise<IFileStatWithMetadata> {
updateContent(resource: uri, value: string | ITextSnapshot, options: IWriteTextFileOptions = Object.create(null)): Promise<IFileStatWithMetadata> {
if (options.writeElevated) {
return this.doUpdateContentElevated(resource, value, options);
}
@@ -390,7 +390,7 @@ export class LegacyFileService extends Disposable implements ILegacyFileService
return this.doUpdateContent(resource, value, options);
}
private doUpdateContent(resource: uri, value: string | ITextSnapshot, options: IUpdateContentOptions = Object.create(null)): Promise<IFileStatWithMetadata> {
private doUpdateContent(resource: uri, value: string | ITextSnapshot, options: IWriteTextFileOptions = Object.create(null)): Promise<IFileStatWithMetadata> {
const absolutePath = this.toAbsolutePath(resource);
// 1.) check file for writing
@@ -501,12 +501,12 @@ export class LegacyFileService extends Disposable implements ILegacyFileService
});
}
private doUpdateContentElevated(resource: uri, value: string | ITextSnapshot, options: IUpdateContentOptions = Object.create(null)): Promise<IFileStatWithMetadata> {
private doUpdateContentElevated(resource: uri, value: string | ITextSnapshot, options: IWriteTextFileOptions = Object.create(null)): Promise<IFileStatWithMetadata> {
const absolutePath = this.toAbsolutePath(resource);
// 1.) check file for writing
return this.checkFileBeforeWriting(absolutePath, options, options.overwriteReadonly /* ignore readonly if we overwrite readonly, this is handled via sudo later */).then(exists => {
const writeOptions: IUpdateContentOptions = objects.assign(Object.create(null), options);
const writeOptions: IWriteTextFileOptions = objects.assign(Object.create(null), options);
writeOptions.writeElevated = false;
writeOptions.encoding = this._encoding.getWriteEncoding(resource, options.encoding).encoding;
@@ -602,7 +602,7 @@ export class LegacyFileService extends Disposable implements ILegacyFileService
//#region Helpers
private checkFileBeforeWriting(absolutePath: string, options: IUpdateContentOptions = Object.create(null), ignoreReadonly?: boolean): Promise<boolean /* exists */> {
private checkFileBeforeWriting(absolutePath: string, options: IWriteTextFileOptions = Object.create(null), ignoreReadonly?: boolean): Promise<boolean /* exists */> {
return pfs.exists(absolutePath).then(exists => {
if (exists) {
return pfs.stat(absolutePath).then(stat => {
@@ -656,7 +656,7 @@ export class LegacyFileService extends Disposable implements ILegacyFileService
});
}
private readOnlyError<T>(options: IUpdateContentOptions): Promise<T> {
private readOnlyError<T>(options: IWriteTextFileOptions): Promise<T> {
return Promise.reject(new FileOperationError(
nls.localize('fileReadOnlyError', "File is Read Only"),
FileOperationResult.FILE_READ_ONLY,

View File

@@ -11,12 +11,24 @@ import { IDecodeStreamOptions, toDecodeStream, encodeStream } from 'vs/base/node
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
import { localize } from 'vs/nls';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FileWriteOptions, FileSystemProviderCapabilities, IContent, ICreateFileOptions, IFileSystemProvider, IResolveContentOptions, IStreamContent, ITextSnapshot, IUpdateContentOptions, StringSnapshot, ILegacyFileService, IFileService, toFileOperationResult, IFileStatWithMetadata } from 'vs/platform/files/common/files';
import { FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FileWriteOptions, FileSystemProviderCapabilities, IContent, ICreateFileOptions, IFileSystemProvider, IResolveContentOptions, IStreamContent, ITextSnapshot, IWriteTextFileOptions, ILegacyFileService, IFileService, toFileOperationResult, IFileStatWithMetadata } from 'vs/platform/files/common/files';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { LegacyFileService } from 'vs/workbench/services/files/node/fileService';
import { createReadableOfProvider, createReadableOfSnapshot, createWritableOfProvider } from 'vs/workbench/services/files/node/streams';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
class StringSnapshot implements ITextSnapshot {
private _value: string | null;
constructor(value: string) {
this._value = value;
}
read(): string | null {
let ret = this._value;
this._value = null;
return ret;
}
}
export class LegacyRemoteFileService extends LegacyFileService {
private readonly _provider: Map<string, IFileSystemProvider>;
@@ -181,7 +193,7 @@ export class LegacyRemoteFileService extends LegacyFileService {
}
}
updateContent(resource: URI, value: string | ITextSnapshot, options?: IUpdateContentOptions): Promise<IFileStatWithMetadata> {
updateContent(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise<IFileStatWithMetadata> {
if (resource.scheme === Schemas.file) {
return super.updateContent(resource, value, options);
} else {

View File

@@ -127,7 +127,7 @@ function createSimpleReadable(provider: IFileSystemProvider, resource: URI, posi
return;
}
this._readOperation = provider.readFile!(resource).then(data => {
this.push(data.slice(position));
this.push(Buffer.from(data.buffer, data.byteOffset, data.byteLength).slice(position));
this.push(null);
}, err => {
this.emit('error', err);

View File

@@ -8,7 +8,7 @@ import * as path from 'vs/base/common/path';
import * as os from 'os';
import * as assert from 'assert';
import { LegacyFileService } from 'vs/workbench/services/files/node/fileService';
import { FileOperation, FileOperationEvent, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files';
import { FileOperationResult, FileOperationError } from 'vs/platform/files/common/files';
import { URI as uri } from 'vs/base/common/uri';
import * as uuid from 'vs/base/common/uuid';
import * as pfs from 'vs/base/node/pfs';
@@ -53,118 +53,6 @@ suite('LegacyFileService', () => {
return pfs.rimraf(parentDir, pfs.RimRafMode.MOVE);
});
test('createFile', () => {
let event: FileOperationEvent;
const toDispose = service.onAfterOperation(e => {
event = e;
});
const contents = 'Hello World';
const resource = uri.file(path.join(testDir, 'test.txt'));
return service.createFile(resource, contents).then(s => {
assert.equal(s.name, 'test.txt');
assert.equal(fs.existsSync(s.resource.fsPath), true);
assert.equal(fs.readFileSync(s.resource.fsPath), contents);
assert.ok(event);
assert.equal(event.resource.fsPath, resource.fsPath);
assert.equal(event.operation, FileOperation.CREATE);
assert.equal(event.target!.resource.fsPath, resource.fsPath);
toDispose.dispose();
});
});
test('createFile (does not overwrite by default)', function () {
const contents = 'Hello World';
const resource = uri.file(path.join(testDir, 'test.txt'));
fs.writeFileSync(resource.fsPath, ''); // create file
return service.createFile(resource, contents).then(undefined, error => {
assert.ok(error);
});
});
test('createFile (allows to overwrite existing)', function () {
let event: FileOperationEvent;
const toDispose = service.onAfterOperation(e => {
event = e;
});
const contents = 'Hello World';
const resource = uri.file(path.join(testDir, 'test.txt'));
fs.writeFileSync(resource.fsPath, ''); // create file
return service.createFile(resource, contents, { overwrite: true }).then(s => {
assert.equal(s.name, 'test.txt');
assert.equal(fs.existsSync(s.resource.fsPath), true);
assert.equal(fs.readFileSync(s.resource.fsPath), contents);
assert.ok(event);
assert.equal(event.resource.fsPath, resource.fsPath);
assert.equal(event.operation, FileOperation.CREATE);
assert.equal(event.target!.resource.fsPath, resource.fsPath);
toDispose.dispose();
});
});
test('updateContent', () => {
const resource = uri.file(path.join(testDir, 'small.txt'));
return service.resolveContent(resource).then(c => {
assert.equal(c.value, 'Small File');
c.value = 'Updates to the small file';
return service.updateContent(c.resource, c.value).then(c => {
assert.equal(fs.readFileSync(resource.fsPath), 'Updates to the small file');
});
});
});
test('updateContent (ITextSnapShot)', function () {
const resource = uri.file(path.join(testDir, 'small.txt'));
return service.resolveContent(resource).then(c => {
assert.equal(c.value, 'Small File');
const model = TextModel.createFromString('Updates to the small file');
return service.updateContent(c.resource, model.createSnapshot()).then(c => {
assert.equal(fs.readFileSync(resource.fsPath), 'Updates to the small file');
model.dispose();
});
});
});
test('updateContent (large file)', function () {
const resource = uri.file(path.join(testDir, 'lorem.txt'));
return service.resolveContent(resource).then(c => {
const newValue = c.value + c.value;
c.value = newValue;
return service.updateContent(c.resource, c.value).then(c => {
assert.equal(fs.readFileSync(resource.fsPath), newValue);
});
});
});
test('updateContent (large file, ITextSnapShot)', function () {
const resource = uri.file(path.join(testDir, 'lorem.txt'));
return service.resolveContent(resource).then(c => {
const newValue = c.value + c.value;
const model = TextModel.createFromString(newValue);
return service.updateContent(c.resource, model.createSnapshot()).then(c => {
assert.equal(fs.readFileSync(resource.fsPath), newValue);
});
});
});
test('updateContent - use encoding (UTF 16 BE)', function () {
const resource = uri.file(path.join(testDir, 'small.txt'));
const encoding = 'utf16be';
@@ -251,6 +139,67 @@ suite('LegacyFileService', () => {
});
});
test('updateContent - UTF 8 BOMs', function () {
// setup
const _id = uuid.generateUuid();
const _testDir = path.join(parentDir, _id);
const _sourceDir = getPathFromAmdModule(require, './fixtures/service');
const resource = uri.file(path.join(testDir, 'index.html'));
const fileService = new FileService2(new NullLogService());
fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService()));
const _service = new LegacyFileService(
fileService,
new TestContextService(new Workspace(_testDir, toWorkspaceFolders([{ path: _testDir }]))),
TestEnvironmentService,
new TestTextResourceConfigurationService()
);
return pfs.copy(_sourceDir, _testDir).then(() => {
return pfs.readFile(resource.fsPath).then(data => {
assert.equal(encodingLib.detectEncodingByBOMFromBuffer(data, 512), null);
const model = TextModel.createFromString('Hello Bom');
// Update content: UTF_8 => UTF_8_BOM
return _service.updateContent(resource, model.createSnapshot(), { encoding: encodingLib.UTF8_with_bom }).then(() => {
return pfs.readFile(resource.fsPath).then(data => {
assert.equal(encodingLib.detectEncodingByBOMFromBuffer(data, 512), encodingLib.UTF8);
// Update content: PRESERVE BOM when using UTF-8
model.setValue('Please stay Bom');
return _service.updateContent(resource, model.createSnapshot(), { encoding: encodingLib.UTF8 }).then(() => {
return pfs.readFile(resource.fsPath).then(data => {
assert.equal(encodingLib.detectEncodingByBOMFromBuffer(data, 512), encodingLib.UTF8);
// Update content: REMOVE BOM
model.setValue('Go away Bom');
return _service.updateContent(resource, model.createSnapshot(), { encoding: encodingLib.UTF8, overwriteEncoding: true }).then(() => {
return pfs.readFile(resource.fsPath).then(data => {
assert.equal(encodingLib.detectEncodingByBOMFromBuffer(data, 512), null);
// Update content: BOM comes not back
model.setValue('Do not come back Bom');
return _service.updateContent(resource, model.createSnapshot(), { encoding: encodingLib.UTF8 }).then(() => {
return pfs.readFile(resource.fsPath).then(data => {
assert.equal(encodingLib.detectEncodingByBOMFromBuffer(data, 512), null);
model.dispose();
_service.dispose();
});
});
});
});
});
});
});
});
});
});
});
test('resolveContent - large file', function () {
const resource = uri.file(path.join(testDir, 'lorem.txt'));
@@ -259,7 +208,7 @@ suite('LegacyFileService', () => {
});
});
test('Files are intermingled #38331', function () {
test('resolveContent - Files are intermingled #38331', function () {
let resource1 = uri.file(path.join(testDir, 'lorem.txt'));
let resource2 = uri.file(path.join(testDir, 'some_utf16le.css'));
let value1: string;
@@ -359,7 +308,7 @@ suite('LegacyFileService', () => {
});
});
test('options - encoding override (parent)', function () {
test('resolveContent - options - encoding override (parent)', function () {
// setup
const _id = uuid.generateUuid();
@@ -381,7 +330,6 @@ suite('LegacyFileService', () => {
const fileService = new FileService2(new NullLogService());
fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService()));
const _service = new LegacyFileService(
fileService,
new TestContextService(new Workspace(_testDir, toWorkspaceFolders([{ path: _testDir }]))),
@@ -402,7 +350,7 @@ suite('LegacyFileService', () => {
});
});
test('options - encoding override (extension)', function () {
test('resolveContent - options - encoding override (extension)', function () {
// setup
const _id = uuid.generateUuid();
@@ -444,67 +392,6 @@ suite('LegacyFileService', () => {
});
});
test('UTF 8 BOMs', function () {
// setup
const _id = uuid.generateUuid();
const _testDir = path.join(parentDir, _id);
const _sourceDir = getPathFromAmdModule(require, './fixtures/service');
const resource = uri.file(path.join(testDir, 'index.html'));
const fileService = new FileService2(new NullLogService());
fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService()));
const _service = new LegacyFileService(
fileService,
new TestContextService(new Workspace(_testDir, toWorkspaceFolders([{ path: _testDir }]))),
TestEnvironmentService,
new TestTextResourceConfigurationService()
);
return pfs.copy(_sourceDir, _testDir).then(() => {
return pfs.readFile(resource.fsPath).then(data => {
assert.equal(encodingLib.detectEncodingByBOMFromBuffer(data, 512), null);
const model = TextModel.createFromString('Hello Bom');
// Update content: UTF_8 => UTF_8_BOM
return _service.updateContent(resource, model.createSnapshot(), { encoding: encodingLib.UTF8_with_bom }).then(() => {
return pfs.readFile(resource.fsPath).then(data => {
assert.equal(encodingLib.detectEncodingByBOMFromBuffer(data, 512), encodingLib.UTF8);
// Update content: PRESERVE BOM when using UTF-8
model.setValue('Please stay Bom');
return _service.updateContent(resource, model.createSnapshot(), { encoding: encodingLib.UTF8 }).then(() => {
return pfs.readFile(resource.fsPath).then(data => {
assert.equal(encodingLib.detectEncodingByBOMFromBuffer(data, 512), encodingLib.UTF8);
// Update content: REMOVE BOM
model.setValue('Go away Bom');
return _service.updateContent(resource, model.createSnapshot(), { encoding: encodingLib.UTF8, overwriteEncoding: true }).then(() => {
return pfs.readFile(resource.fsPath).then(data => {
assert.equal(encodingLib.detectEncodingByBOMFromBuffer(data, 512), null);
// Update content: BOM comes not back
model.setValue('Do not come back Bom');
return _service.updateContent(resource, model.createSnapshot(), { encoding: encodingLib.UTF8 }).then(() => {
return pfs.readFile(resource.fsPath).then(data => {
assert.equal(encodingLib.detectEncodingByBOMFromBuffer(data, 512), null);
model.dispose();
_service.dispose();
});
});
});
});
});
});
});
});
});
});
});
test('resolveContent - from position (ASCII)', function () {
const resource = uri.file(path.join(testDir, 'small.txt'));

View File

@@ -0,0 +1,78 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { FileService2 } from 'vs/workbench/services/files2/common/fileService2';
import { URI } from 'vs/base/common/uri';
import { IResolveContentOptions, IStreamContent, IStringStream, IContent, IFileSystemProvider, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
import { basename } from 'vs/base/common/resources';
import { VSBuffer } from 'vs/base/common/buffer';
import { localize } from 'vs/nls';
// TODO@ben temporary for testing only
export class FileService3 extends FileService2 {
async resolveContent(resource: URI, options?: IResolveContentOptions): Promise<IContent> {
return this.resolveStreamContent(resource, options).then(streamContent => {
return new Promise<IContent>((resolve, reject) => {
const result: IContent = {
resource: streamContent.resource,
name: streamContent.name,
mtime: streamContent.mtime,
etag: streamContent.etag,
encoding: streamContent.encoding,
isReadonly: streamContent.isReadonly,
size: streamContent.size,
value: ''
};
streamContent.value.on('data', chunk => result.value += chunk);
streamContent.value.on('error', err => reject(err));
streamContent.value.on('end', () => resolve(result));
return result;
});
});
}
async resolveStreamContent(resource: URI, options?: IResolveContentOptions): Promise<IStreamContent> {
const provider = await this.withProvider(resource);
if (provider && provider.readFile) {
const listeners: { [type: string]: any[]; } = Object.create(null);
const stringStream: IStringStream = {
on(event: string, callback: any): void {
listeners[event] = listeners[event] || [];
listeners[event].push(callback);
}
};
const stat = await this.resolve(resource, { resolveMetadata: true });
const r: IStreamContent = {
mtime: stat.mtime,
size: stat.size,
etag: stat.etag,
value: stringStream,
resource: resource,
encoding: 'utf8',
name: basename(resource)
};
provider.readFile(resource).then((contents) => {
const str = VSBuffer.wrap(contents).toString();
listeners['data'].forEach((listener) => listener(str));
listeners['end'].forEach((listener) => listener());
});
return r;
}
return super.resolveStreamContent(resource, options);
}
protected throwIfFileSystemIsReadonly(provider: IFileSystemProvider): IFileSystemProvider {
// we really do not want to allow for changes currently
throw new FileOperationError(localize('err.readonly', "Resource can not be modified."), FileOperationResult.FILE_PERMISSION_DENIED);
}
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable, IDisposable, toDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle';
import { IFileService, IResolveFileOptions, IResourceEncodings, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, IResolveContentOptions, IContent, IStreamContent, ITextSnapshot, IUpdateContentOptions, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, ILegacyFileService } from 'vs/platform/files/common/files';
import { IFileService, IResolveFileOptions, IResourceEncodings, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, IResolveContentOptions, IContent, IStreamContent, ITextSnapshot, IWriteTextFileOptions, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, ILegacyFileService, IWriteFileOptions } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
@@ -14,7 +14,7 @@ import { TernarySearchTree } from 'vs/base/common/map';
import { isNonEmptyArray, coalesce } from 'vs/base/common/arrays';
import { getBaseLabel } from 'vs/base/common/labels';
import { ILogService } from 'vs/platform/log/common/log';
import { VSBuffer } from 'vs/base/common/buffer';
import { VSBuffer, VSBufferReadable, readableToBuffer, bufferToReadable } from 'vs/base/common/buffer';
export class FileService2 extends Disposable implements IFileService {
@@ -129,7 +129,7 @@ export class FileService2 extends Disposable implements IFileService {
return !!(provider && (provider.capabilities & capability));
}
private async withProvider(resource: URI): Promise<IFileSystemProvider> {
protected async withProvider(resource: URI): Promise<IFileSystemProvider> {
// Assert path is absolute
if (!isAbsolutePath(resource)) {
@@ -200,7 +200,7 @@ export class FileService2 extends Disposable implements IFileService {
const stat = await provider.stat(resource);
return await this.toFileStat(provider, resource, stat, undefined, resolveMetadata, (stat, siblings) => {
return this.toFileStat(provider, resource, stat, undefined, resolveMetadata, (stat, siblings) => {
// check for recursive resolving
if (Boolean(trie.findSuperstr(stat.resource.toString()) || trie.get(stat.resource.toString()))) {
@@ -295,38 +295,68 @@ export class FileService2 extends Disposable implements IFileService {
return this._legacy.encoding;
}
async createFile(resource: URI, content?: string, options?: ICreateFileOptions): Promise<IFileStatWithMetadata> {
const useLegacy = true; // can only disable this when encoding is sorted out
if (useLegacy) {
return this.joinOnLegacy.then(legacy => legacy.createFile(resource, content, options));
}
const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(resource));
async createFile2(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable = VSBuffer.fromString(''), options?: ICreateFileOptions): Promise<IFileStatWithMetadata> {
// validate overwrite
const overwrite = !!(options && options.overwrite);
if (await this.exists(resource)) {
if (!overwrite) {
throw new FileOperationError(localize('fileExists', "File to create already exists ({0})", resource.toString(true)), FileOperationResult.FILE_MODIFIED_SINCE, options);
if (!overwrite && await this.exists(resource)) {
throw new FileOperationError(localize('fileExists', "File to create already exists ({0})", resource.toString(true)), FileOperationResult.FILE_MODIFIED_SINCE, options);
}
// do write into file (this will create it too)
const fileStat = await this.writeFile(resource, bufferOrReadable);
// events
this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.CREATE, fileStat));
return fileStat;
}
async writeFile(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable, options?: IWriteFileOptions): Promise<IFileStatWithMetadata> {
const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(resource));
// validate write
const exists = await this.exists(resource);
if (exists) {
const stat = await provider.stat(resource);
// file cannot be directory
if ((stat.type & FileType.Directory) !== 0) {
throw new Error(localize('fileIsDirectoryError', "Expected file {0} is actually a directory", resource.toString()));
}
// delete otherwise
await this.del(resource, { recursive: true });
// Dirty write prevention: if the file on disk has been changed and does not match our expected
// mtime and etag, we bail out to prevent dirty writing.
//
// First, we check for a mtime that is in the future before we do more checks. The assumption is
// that only the mtime is an indicator for a file that has changd on disk.
//
// Second, if the mtime has advanced, we compare the size of the file on disk with our previous
// one using the etag() function. Relying only on the mtime check has prooven to produce false
// positives due to file system weirdness (especially around remote file systems). As such, the
// check for size is a weaker check because it can return a false negative if the file has changed
// but to the same length. This is a compromise we take to avoid having to produce checksums of
// the file content for comparison which would be much slower to compute.
if (options && typeof options.mtime === 'number' && typeof options.etag === 'string' && options.mtime < stat.mtime && options.etag !== etag(stat.size, options.mtime)) {
throw new FileOperationError(localize('fileModifiedError', "File Modified Since"), FileOperationResult.FILE_MODIFIED_SINCE, options);
}
}
try {
// mkdir recursively
await this.mkdirp(provider, dirname(resource));
// create file: buffered
if (hasOpenReadWriteCloseCapability(provider)) {
await this.doWriteBuffered(provider, resource, VSBuffer.fromString(content || ''));
// mkdir recursively as needed
if (!exists) {
await this.mkdirp(provider, dirname(resource));
}
// create file: unbuffered
// write file: buffered
if (hasOpenReadWriteCloseCapability(provider)) {
await this.doWriteBuffered(provider, resource, bufferOrReadable instanceof VSBuffer ? bufferToReadable(bufferOrReadable) : bufferOrReadable);
}
// write file: unbuffered
else if (hasReadWriteCapability(provider)) {
await this.doWriteUnbuffered(provider, resource, VSBuffer.fromString(content || ''), overwrite);
await this.doWriteUnbuffered(provider, resource, bufferOrReadable);
}
// give up if provider has insufficient capabilities
@@ -334,25 +364,25 @@ export class FileService2 extends Disposable implements IFileService {
return Promise.reject('Provider neither has FileReadWrite nor FileOpenReadWriteClose capability which is needed to support creating a file.');
}
} catch (error) {
throw new FileOperationError(localize('err.create', "Failed to create file {0}", resource.toString(false)), toFileOperationResult(error), options);
throw new FileOperationError(localize('err.write', "Failed to write file {0}", resource.toString(false)), toFileOperationResult(error), options);
}
// events
const fileStat = await this.resolve(resource, { resolveMetadata: true });
this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.CREATE, fileStat));
return this.resolve(resource, { resolveMetadata: true });
}
return fileStat;
createFile(resource: URI, content?: string, options?: ICreateFileOptions): Promise<IFileStatWithMetadata> {
return this.joinOnLegacy.then(legacy => legacy.createFile(resource, content, options));
}
resolveContent(resource: URI, options?: IResolveContentOptions): Promise<IContent> {
return this.joinOnLegacy.then(legacy => legacy.resolveContent(resource, options));
}
resolveStreamContent(resource: URI, options?: IResolveContentOptions): Promise<IStreamContent> {
async resolveStreamContent(resource: URI, options?: IResolveContentOptions): Promise<IStreamContent> {
return this.joinOnLegacy.then(legacy => legacy.resolveStreamContent(resource, options));
}
updateContent(resource: URI, value: string | ITextSnapshot, options?: IUpdateContentOptions): Promise<IFileStatWithMetadata> {
updateContent(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise<IFileStatWithMetadata> {
return this.joinOnLegacy.then(legacy => legacy.updateContent(resource, value, options));
}
@@ -414,16 +444,16 @@ export class FileService2 extends Disposable implements IFileService {
!(hasOpenReadWriteCloseCapability(sourceProvider) || hasReadWriteCapability(sourceProvider)) ||
!(hasOpenReadWriteCloseCapability(targetProvider) || hasReadWriteCapability(targetProvider))
) {
return Promise.reject('Provider neither has FileReadWrite nor FileOpenReadWriteClose capability which is needed to support copy.');
throw new Error('Provider neither has FileReadWrite nor FileOpenReadWriteClose capability which is needed to support copy.');
}
// when copying via buffer/unbuffered, we have to manually
// traverse the source if it is a folder and not a file
const sourceFile = await this.resolve(source);
if (sourceFile.isDirectory) {
return this.doCopyFolder(sourceProvider, sourceFile, targetProvider, target, overwrite).then(() => mode);
return this.doCopyFolder(sourceProvider, sourceFile, targetProvider, target).then(() => mode);
} else {
return this.doCopyFile(sourceProvider, source, targetProvider, target, overwrite).then(() => mode);
return this.doCopyFile(sourceProvider, source, targetProvider, target).then(() => mode);
}
}
@@ -444,7 +474,7 @@ export class FileService2 extends Disposable implements IFileService {
}
}
private async doCopyFile(sourceProvider: IFileSystemProvider, source: URI, targetProvider: IFileSystemProvider, target: URI, overwrite?: boolean): Promise<void> {
private async doCopyFile(sourceProvider: IFileSystemProvider, source: URI, targetProvider: IFileSystemProvider, target: URI): Promise<void> {
// copy: source (buffered) => target (buffered)
if (hasOpenReadWriteCloseCapability(sourceProvider) && hasOpenReadWriteCloseCapability(targetProvider)) {
@@ -453,7 +483,7 @@ export class FileService2 extends Disposable implements IFileService {
// copy: source (buffered) => target (unbuffered)
if (hasOpenReadWriteCloseCapability(sourceProvider) && hasReadWriteCapability(targetProvider)) {
return this.doPipeBufferedToUnbuffered(sourceProvider, source, targetProvider, target, !!overwrite);
return this.doPipeBufferedToUnbuffered(sourceProvider, source, targetProvider, target);
}
// copy: source (unbuffered) => target (buffered)
@@ -463,11 +493,11 @@ export class FileService2 extends Disposable implements IFileService {
// copy: source (unbuffered) => target (unbuffered)
if (hasReadWriteCapability(sourceProvider) && hasReadWriteCapability(targetProvider)) {
return this.doPipeUnbuffered(sourceProvider, source, targetProvider, target, !!overwrite);
return this.doPipeUnbuffered(sourceProvider, source, targetProvider, target);
}
}
private async doCopyFolder(sourceProvider: IFileSystemProvider, sourceFolder: IFileStat, targetProvider: IFileSystemProvider, targetFolder: URI, overwrite?: boolean): Promise<void> {
private async doCopyFolder(sourceProvider: IFileSystemProvider, sourceFolder: IFileStat, targetProvider: IFileSystemProvider, targetFolder: URI): Promise<void> {
// create folder in target
await targetProvider.mkdir(targetFolder);
@@ -477,9 +507,9 @@ export class FileService2 extends Disposable implements IFileService {
await Promise.all(sourceFolder.children.map(async sourceChild => {
const targetChild = joinPath(targetFolder, sourceChild.name);
if (sourceChild.isDirectory) {
return this.doCopyFolder(sourceProvider, await this.resolve(sourceChild.resource), targetProvider, targetChild, overwrite);
return this.doCopyFolder(sourceProvider, await this.resolve(sourceChild.resource), targetProvider, targetChild);
} else {
return this.doCopyFile(sourceProvider, sourceChild.resource, targetProvider, targetChild, overwrite);
return this.doCopyFile(sourceProvider, sourceChild.resource, targetProvider, targetChild);
}
}));
}
@@ -494,7 +524,7 @@ export class FileService2 extends Disposable implements IFileService {
const isPathCaseSensitive = !!(sourceProvider.capabilities & FileSystemProviderCapabilities.PathCaseSensitive);
isCaseChange = isPathCaseSensitive ? false : isEqual(source, target, true /* ignore case */);
if (!isCaseChange && isEqualOrParent(target, source, !isPathCaseSensitive)) {
return Promise.reject(new Error(localize('unableToMoveCopyError1', "Unable to move/copy when source path is equal or parent of target path")));
throw new Error(localize('unableToMoveCopyError1', "Unable to move/copy when source path is equal or parent of target path"));
}
}
@@ -510,7 +540,7 @@ export class FileService2 extends Disposable implements IFileService {
// Special case: if the target is a parent of the source, we cannot delete
// it as it would delete the source as well. In this case we have to throw
if (sourceProvider === targetProvider && isEqualOrParent(source, target, !isPathCaseSensitive)) {
return Promise.reject(new Error(localize('unableToMoveCopyError3', "Unable to move/copy. File would replace folder it is contained in.")));
throw new Error(localize('unableToMoveCopyError3', "Unable to move/copy. File would replace folder it is contained in."));
}
}
@@ -662,14 +692,21 @@ export class FileService2 extends Disposable implements IFileService {
//#region Helpers
private async doWriteBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, buffer: VSBuffer): Promise<void> {
private async doWriteBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, readable: VSBufferReadable): Promise<void> {
// open handle
const handle = await provider.open(resource, { create: true });
// write into handle until all bytes from buffer have been written
try {
await this.doWriteBuffer(provider, handle, buffer, buffer.byteLength, 0, 0);
let posInFile = 0;
let chunk: VSBuffer | null;
while (chunk = readable.read()) {
await this.doWriteBuffer(provider, handle, chunk, chunk.byteLength, posInFile, 0);
posInFile += chunk.byteLength;
}
} catch (error) {
throw error;
} finally {
@@ -685,8 +722,15 @@ export class FileService2 extends Disposable implements IFileService {
}
}
private async doWriteUnbuffered(provider: IFileSystemProviderWithFileReadWriteCapability, resource: URI, buffer: VSBuffer, overwrite: boolean): Promise<void> {
return provider.writeFile(resource, buffer.buffer, { create: true, overwrite });
private async doWriteUnbuffered(provider: IFileSystemProviderWithFileReadWriteCapability, resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable): Promise<void> {
let buffer: VSBuffer;
if (bufferOrReadable instanceof VSBuffer) {
buffer = bufferOrReadable;
} else {
buffer = readableToBuffer(bufferOrReadable);
}
return provider.writeFile(resource, buffer.buffer, { create: true, overwrite: true });
}
private async doPipeBuffered(sourceProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, source: URI, targetProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI): Promise<void> {
@@ -731,8 +775,8 @@ export class FileService2 extends Disposable implements IFileService {
}
}
private async doPipeUnbuffered(sourceProvider: IFileSystemProviderWithFileReadWriteCapability, source: URI, targetProvider: IFileSystemProviderWithFileReadWriteCapability, target: URI, overwrite: boolean): Promise<void> {
return targetProvider.writeFile(target, await sourceProvider.readFile(source), { create: true, overwrite });
private async doPipeUnbuffered(sourceProvider: IFileSystemProviderWithFileReadWriteCapability, source: URI, targetProvider: IFileSystemProviderWithFileReadWriteCapability, target: URI): Promise<void> {
return targetProvider.writeFile(target, await sourceProvider.readFile(source), { create: true, overwrite: true });
}
private async doPipeUnbufferedToBuffered(sourceProvider: IFileSystemProviderWithFileReadWriteCapability, source: URI, targetProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI): Promise<void> {
@@ -751,7 +795,7 @@ export class FileService2 extends Disposable implements IFileService {
}
}
private async doPipeBufferedToUnbuffered(sourceProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, source: URI, targetProvider: IFileSystemProviderWithFileReadWriteCapability, target: URI, overwrite: boolean): Promise<void> {
private async doPipeBufferedToUnbuffered(sourceProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, source: URI, targetProvider: IFileSystemProviderWithFileReadWriteCapability, target: URI): Promise<void> {
// Open handle
const sourceHandle = await sourceProvider.open(source, { create: false });
@@ -784,7 +828,7 @@ export class FileService2 extends Disposable implements IFileService {
} while (bytesRead > 0);
// Write buffer into target at once
await this.doWriteUnbuffered(targetProvider, target, VSBuffer.concat([...buffers, buffer.slice(0, posInBuffer)], totalBytesRead), overwrite);
await this.doWriteUnbuffered(targetProvider, target, VSBuffer.concat([...buffers, buffer.slice(0, posInBuffer)], totalBytesRead));
} catch (error) {
throw error;
} finally {
@@ -792,7 +836,7 @@ export class FileService2 extends Disposable implements IFileService {
}
}
private throwIfFileSystemIsReadonly(provider: IFileSystemProvider): IFileSystemProvider {
protected throwIfFileSystemIsReadonly(provider: IFileSystemProvider): IFileSystemProvider {
if (provider.capabilities & FileSystemProviderCapabilities.Readonly) {
throw new FileOperationError(localize('err.readonly', "Resource can not be modified."), FileOperationResult.FILE_PERMISSION_DENIED);
}

View File

@@ -369,36 +369,39 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
// Dispose old
dispose(this.recursiveWatcher);
let watcherImpl: {
new(
folders: { path: string, excludes: string[] }[],
onChange: (changes: IDiskFileChange[]) => void,
onError: (msg: string) => void,
verboseLogging: boolean
): WindowsWatcherService | UnixWatcherService | NsfwWatcherService
};
// Create new if we actually have folders to watch
if (this.recursiveFoldersToWatch.length > 0) {
let watcherImpl: {
new(
folders: { path: string, excludes: string[] }[],
onChange: (changes: IDiskFileChange[]) => void,
onError: (msg: string) => void,
verboseLogging: boolean
): WindowsWatcherService | UnixWatcherService | NsfwWatcherService
};
// Single Folder Watcher
if (this.recursiveFoldersToWatch.length === 1) {
if (isWindows) {
watcherImpl = WindowsWatcherService;
} else {
watcherImpl = UnixWatcherService;
// Single Folder Watcher
if (this.recursiveFoldersToWatch.length === 1) {
if (isWindows) {
watcherImpl = WindowsWatcherService;
} else {
watcherImpl = UnixWatcherService;
}
}
}
// Multi Folder Watcher
else {
watcherImpl = NsfwWatcherService;
}
// Multi Folder Watcher
else {
watcherImpl = NsfwWatcherService;
}
// Create and start watching
this.recursiveWatcher = new watcherImpl(
this.recursiveFoldersToWatch,
event => this._onDidChangeFile.fire(toFileChanges(event)),
error => this._onDidWatchErrorOccur.fire(new Error(error)),
this.logService.getLevel() === LogLevel.Trace
);
// Create and start watching
this.recursiveWatcher = new watcherImpl(
this.recursiveFoldersToWatch,
event => this._onDidChangeFile.fire(toFileChanges(event)),
error => this._onDidWatchErrorOccur.fire(new Error(error)),
this.logService.getLevel() === LogLevel.Trace
);
}
}
}

View File

@@ -15,11 +15,12 @@ import { getPathFromAmdModule } from 'vs/base/common/amd';
import { copy, rimraf, symlink, RimRafMode, rimrafSync } from 'vs/base/node/pfs';
import { URI } from 'vs/base/common/uri';
import { existsSync, statSync, readdirSync, readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync } from 'fs';
import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities, FileChangeType, IFileChange, FileChangesEvent } from 'vs/platform/files/common/files';
import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities, FileChangeType, IFileChange, FileChangesEvent, FileOperationError, etag } from 'vs/platform/files/common/files';
import { NullLogService } from 'vs/platform/log/common/log';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { isEqual } from 'vs/base/common/resources';
import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer';
function getByName(root: IFileStat, name: string): IFileStat | null {
if (root.children === undefined) {
@@ -35,6 +36,28 @@ function getByName(root: IFileStat, name: string): IFileStat | null {
return null;
}
function toLineByLineReadable(content: string): VSBufferReadable {
let chunks = content.split('\n');
chunks = chunks.map((chunk, index) => {
if (index === 0) {
return chunk;
}
return '\n' + chunk;
});
return {
read(): VSBuffer | null {
const chunk = chunks.shift();
if (typeof chunk === 'string') {
return VSBuffer.fromString(chunk);
}
return null;
}
};
}
export class TestDiskFileSystemProvider extends DiskFileSystemProvider {
private _testCapabilities: FileSystemProviderCapabilities;
@@ -777,6 +800,165 @@ suite('Disk File Service', () => {
}
});
test('createFile2', async () => {
let event: FileOperationEvent;
disposables.push(service.onAfterOperation(e => event = e));
const contents = 'Hello World';
const resource = URI.file(join(testDir, 'test.txt'));
const fileStat = await service.createFile2(resource, VSBuffer.fromString(contents));
assert.equal(fileStat.name, 'test.txt');
assert.equal(existsSync(fileStat.resource.fsPath), true);
assert.equal(readFileSync(fileStat.resource.fsPath), contents);
assert.ok(event!);
assert.equal(event!.resource.fsPath, resource.fsPath);
assert.equal(event!.operation, FileOperation.CREATE);
assert.equal(event!.target!.resource.fsPath, resource.fsPath);
});
test('createFile2 (does not overwrite by default)', async () => {
const contents = 'Hello World';
const resource = URI.file(join(testDir, 'test.txt'));
writeFileSync(resource.fsPath, ''); // create file
try {
await service.createFile2(resource, VSBuffer.fromString(contents));
}
catch (error) {
assert.ok(error);
}
});
test('createFile2 (allows to overwrite existing)', async () => {
let event: FileOperationEvent;
disposables.push(service.onAfterOperation(e => event = e));
const contents = 'Hello World';
const resource = URI.file(join(testDir, 'test.txt'));
writeFileSync(resource.fsPath, ''); // create file
const fileStat = await service.createFile2(resource, VSBuffer.fromString(contents), { overwrite: true });
assert.equal(fileStat.name, 'test.txt');
assert.equal(existsSync(fileStat.resource.fsPath), true);
assert.equal(readFileSync(fileStat.resource.fsPath), contents);
assert.ok(event!);
assert.equal(event!.resource.fsPath, resource.fsPath);
assert.equal(event!.operation, FileOperation.CREATE);
assert.equal(event!.target!.resource.fsPath, resource.fsPath);
});
test('writeFile', async () => {
const resource = URI.file(join(testDir, 'small.txt'));
const content = readFileSync(resource.fsPath);
assert.equal(content, 'Small File');
const newContent = 'Updates to the small file';
await service.writeFile(resource, VSBuffer.fromString(newContent));
assert.equal(readFileSync(resource.fsPath), newContent);
});
test('writeFile (large file)', async () => {
const resource = URI.file(join(testDir, 'lorem.txt'));
const content = readFileSync(resource.fsPath);
const newContent = content.toString() + content.toString();
const fileStat = await service.writeFile(resource, VSBuffer.fromString(newContent));
assert.equal(fileStat.name, 'lorem.txt');
assert.equal(readFileSync(resource.fsPath), newContent);
});
test('writeFile (readable)', async () => {
const resource = URI.file(join(testDir, 'small.txt'));
const content = readFileSync(resource.fsPath);
assert.equal(content, 'Small File');
const newContent = 'Updates to the small file';
await service.writeFile(resource, toLineByLineReadable(newContent));
assert.equal(readFileSync(resource.fsPath), newContent);
});
test('writeFile (large file - readable)', async () => {
const resource = URI.file(join(testDir, 'lorem.txt'));
const content = readFileSync(resource.fsPath);
const newContent = content.toString() + content.toString();
const fileStat = await service.writeFile(resource, toLineByLineReadable(newContent));
assert.equal(fileStat.name, 'lorem.txt');
assert.equal(readFileSync(resource.fsPath), newContent);
});
test('writeFile (file is created including parents)', async () => {
const resource = URI.file(join(testDir, 'other', 'newfile.txt'));
const content = 'File is created including parent';
const fileStat = await service.writeFile(resource, VSBuffer.fromString(content));
assert.equal(fileStat.name, 'newfile.txt');
assert.equal(readFileSync(resource.fsPath), content);
});
test('writeFile (error when folder is encountered)', async () => {
const resource = URI.file(testDir);
let error: Error | undefined = undefined;
try {
await service.writeFile(resource, VSBuffer.fromString('File is created including parent'));
} catch (err) {
error = err;
}
assert.ok(error);
});
test('writeFile (no error when providing up to date etag)', async () => {
const resource = URI.file(join(testDir, 'small.txt'));
const stat = await service.resolve(resource);
const content = readFileSync(resource.fsPath);
assert.equal(content, 'Small File');
const newContent = 'Updates to the small file';
await service.writeFile(resource, VSBuffer.fromString(newContent), { etag: stat.etag, mtime: stat.mtime });
assert.equal(readFileSync(resource.fsPath), newContent);
});
test('writeFile (error when writing to file that has been updated meanwhile)', async () => {
const resource = URI.file(join(testDir, 'small.txt'));
const stat = await service.resolve(resource);
const content = readFileSync(resource.fsPath);
assert.equal(content, 'Small File');
const newContent = 'Updates to the small file';
await service.writeFile(resource, VSBuffer.fromString(newContent), { etag: stat.etag, mtime: stat.mtime });
let error: FileOperationError | undefined = undefined;
try {
await service.writeFile(resource, VSBuffer.fromString(newContent), { etag: etag(0, 0), mtime: 0 });
} catch (err) {
error = err;
}
assert.ok(error);
assert.ok(error instanceof FileOperationError);
assert.equal(error!.fileOperationResult, FileOperationResult.FILE_MODIFIED_SINCE);
});
test('watch - file', done => {
const toWatch = URI.file(join(testDir, 'index-watch1.html'));
writeFileSync(toWatch.fsPath, 'Init');

View File

@@ -211,7 +211,7 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding
return this.fileService.exists(this.resource)
.then(exists => {
const EOL = this.configurationService.getValue('files', { overrideIdentifier: 'json' })['eol'];
const result: Promise<any> = exists ? Promise.resolve(null) : this.fileService.updateContent(this.resource, this.getEmptyContent(EOL), { encoding: 'utf8' });
const result: Promise<any> = exists ? Promise.resolve(null) : this.textFileService.write(this.resource, this.getEmptyContent(EOL));
return result.then(() => this.textModelResolverService.createModelReference(this.resource));
});
}

View File

@@ -16,8 +16,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
import { IOutputChannelModel, AbstractFileOutputChannelModel, IOutputChannelModelService, AsbtractOutputChannelModelService, BufferredOutputChannel } from 'vs/workbench/services/output/common/outputChannelModel';
import { OutputAppender } from 'vs/workbench/services/output/node/outputAppender';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { toLocalISOString } from 'vs/base/common/date';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
@@ -206,8 +205,7 @@ export class OutputChannelModelService extends AsbtractOutputChannelModelService
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IWindowService private readonly windowService: IWindowService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IFileService private readonly fileService: IFileService
) {
super(instantiationService);
@@ -221,7 +219,7 @@ export class OutputChannelModelService extends AsbtractOutputChannelModelService
private _outputDir: Promise<URI> | null;
private get outputDir(): Promise<URI> {
if (!this._outputDir) {
const outputDir = URI.file(join(this.environmentService.logsPath, `output_${this.windowService.getCurrentWindowId()}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`));
const outputDir = URI.file(join(this.environmentService.logsPath, `output_${this.environmentService.configuration.windowId}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`));
this._outputDir = this.fileService.createFolder(outputDir).then(() => outputDir);
}
return this._outputDir;

View File

@@ -37,6 +37,7 @@ import { DefaultPreferencesEditorInput, KeybindingsEditorInput, PreferencesEdito
import { defaultKeybindingsContents, DefaultKeybindingsEditorModel, DefaultSettings, DefaultSettingsEditorModel, Settings2EditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel, DefaultRawSettingsEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
const emptyEditableSettingsContent = '{\n}';
@@ -59,6 +60,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
@IEditorService private readonly editorService: IEditorService,
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
@IFileService private readonly fileService: IFileService,
@ITextFileService private readonly textFileService: ITextFileService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@INotificationService private readonly notificationService: INotificationService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@@ -215,8 +217,8 @@ export class PreferencesService extends Disposable implements IPreferencesServic
async openRemoteSettings(): Promise<IEditor | null> {
const environemnt = await this.remoteAgentService.getEnvironment();
if (environemnt) {
await this.createIfNotExists(environemnt.appSettingsPath, emptyEditableSettingsContent);
return this.editorService.openEditor({ resource: environemnt.appSettingsPath, options: { pinned: true, revealIfOpened: true } });
await this.createIfNotExists(environemnt.settingsPath, emptyEditableSettingsContent);
return this.editorService.openEditor({ resource: environemnt.settingsPath, options: { pinned: true, revealIfOpened: true } });
}
return null;
}
@@ -555,7 +557,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
private createIfNotExists(resource: URI, contents: string): Promise<any> {
return this.fileService.resolveContent(resource, { acceptTextOnly: true }).then(undefined, error => {
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) {
return this.fileService.updateContent(resource, contents).then(undefined, error => {
return this.textFileService.write(resource, contents).then(undefined, error => {
return Promise.reject(new Error(nls.localize('fail.createSettings', "Unable to create '{0}' ({1}).", this.labelService.getUriLabel(resource, { relative: true }), error)));
});
}

View File

@@ -0,0 +1,25 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IRemoteAgentConnection } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { AbstractRemoteAgentService } from 'vs/workbench/services/remote/common/abstractRemoteAgentService';
import { IProductService } from 'vs/platform/product/common/product';
export class RemoteAgentService extends AbstractRemoteAgentService {
constructor(
@IEnvironmentService environmentService: IEnvironmentService,
@IProductService productService: IProductService,
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService
) {
super(environmentService);
}
getConnection(): IRemoteAgentConnection | null {
return null;
}
}

View File

@@ -17,6 +17,8 @@ import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } f
import { Registry } from 'vs/platform/registry/common/platform';
import { RemoteExtensionEnvironmentChannelClient } from 'vs/workbench/services/remote/common/remoteAgentEnvironmentChannel';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService';
import { Emitter } from 'vs/base/common/event';
export abstract class AbstractRemoteAgentService extends Disposable implements IRemoteAgentService {
@@ -44,10 +46,23 @@ export abstract class AbstractRemoteAgentService extends Disposable implements I
}
return bail ? this._environment : this._environment.then(undefined, () => null);
}
getDiagnosticInfo(options: IDiagnosticInfoOptions): Promise<IDiagnosticInfo | undefined> {
const connection = this.getConnection();
if (connection) {
const client = new RemoteExtensionEnvironmentChannelClient(connection.getChannel('remoteextensionsenvironment'));
return client.getDiagnosticInfo(options);
}
return Promise.resolve(undefined);
}
}
export class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection {
private readonly _onReconnecting = this._register(new Emitter<void>());
public readonly onReconnecting = this._onReconnecting.event;
readonly remoteAuthority: string;
private _connection: Promise<Client<RemoteAgentConnectionContext>> | null;
@@ -79,12 +94,18 @@ export class RemoteAgentConnection extends Disposable implements IRemoteAgentCon
}
private async _createConnection(): Promise<Client<RemoteAgentConnectionContext>> {
let firstCall = true;
const options: IConnectionOptions = {
isBuilt: this._environmentService.isBuilt,
commit: this._commit,
webSocketFactory: this._webSocketFactory,
addressProvider: {
getAddress: async () => {
if (firstCall) {
firstCall = false;
} else {
this._onReconnecting.fire(undefined);
}
const { host, port } = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority);
return { host, port };
}

View File

@@ -8,6 +8,7 @@ import { URI, UriComponents } from 'vs/base/common/uri';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService';
export interface IGetEnvironmentDataArguments {
language: string;
@@ -19,7 +20,7 @@ export interface IRemoteAgentEnvironmentDTO {
pid: number;
appRoot: UriComponents;
appSettingsHome: UriComponents;
appSettingsPath: UriComponents;
settingsPath: UriComponents;
logsPath: UriComponents;
extensionsPath: UriComponents;
extensionHostLogsPath: UriComponents;
@@ -34,7 +35,7 @@ export class RemoteExtensionEnvironmentChannelClient {
constructor(private channel: IChannel) { }
getEnvironmentData(remoteAuthority: string, extensionDevelopmentPath?: URI | URI[]): Promise<IRemoteAgentEnvironment> {
getEnvironmentData(remoteAuthority: string, extensionDevelopmentPath?: URI[]): Promise<IRemoteAgentEnvironment> {
const args: IGetEnvironmentDataArguments = {
language: platform.language,
remoteAuthority,
@@ -46,7 +47,7 @@ export class RemoteExtensionEnvironmentChannelClient {
pid: data.pid,
appRoot: URI.revive(data.appRoot),
appSettingsHome: URI.revive(data.appSettingsHome),
appSettingsPath: URI.revive(data.appSettingsPath),
settingsPath: URI.revive(data.settingsPath),
logsPath: URI.revive(data.logsPath),
extensionsPath: URI.revive(data.extensionsPath),
extensionHostLogsPath: URI.revive(data.extensionHostLogsPath),
@@ -58,4 +59,8 @@ export class RemoteExtensionEnvironmentChannelClient {
};
});
}
getDiagnosticInfo(options: IDiagnosticInfoOptions): Promise<IDiagnosticInfo> {
return this.channel.call<IDiagnosticInfo>('getDiagnosticInfo', options);
}
}

View File

@@ -6,6 +6,8 @@
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { RemoteAgentConnectionContext, IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService';
import { Event } from 'vs/base/common/event';
export const RemoteExtensionLogFileName = 'remoteagent';
@@ -16,6 +18,7 @@ export interface IRemoteAgentService {
getConnection(): IRemoteAgentConnection | null;
getEnvironment(bail?: boolean): Promise<IRemoteAgentEnvironment | null>;
getDiagnosticInfo(options: IDiagnosticInfoOptions): Promise<IDiagnosticInfo | undefined>;
}
export interface IRemoteAgentConnection {
@@ -23,4 +26,5 @@ export interface IRemoteAgentConnection {
getChannel<T extends IChannel>(channelName: string): T;
registerChannel<T extends IServerChannel<RemoteAgentConnectionContext>>(channelName: string, channel: T): void;
onReconnecting: Event<void>;
}

View File

@@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
export class TunnelService implements ITunnelService {
_serviceBrand: any;
public constructor(
) {
}
openTunnel(remotePort: number): Promise<RemoteTunnel> | undefined {
return undefined;
}
}

View File

@@ -0,0 +1,63 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITelemetryService, ITelemetryInfo, ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IProductService } from 'vs/platform/product/common/product';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc';
import { ILogService } from 'vs/platform/log/common/log';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/node/workbenchCommonProperties';
import { TelemetryService as BaseTelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
export class TelemetryService extends Disposable implements ITelemetryService {
_serviceBrand: any;
private impl: ITelemetryService;
constructor(
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IProductService productService: IProductService,
@ISharedProcessService sharedProcessService: ISharedProcessService,
@ILogService logService: ILogService,
@IStorageService storageService: IStorageService,
@IConfigurationService configurationService: IConfigurationService,
) {
super();
if (!environmentService.isExtensionDevelopment && !environmentService.args['disable-telemetry'] && !!productService.enableTelemetry) {
const channel = sharedProcessService.getChannel('telemetryAppender');
const config: ITelemetryServiceConfig = {
appender: combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(logService)),
commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId, environmentService.installSourcePath),
piiPaths: [environmentService.appRoot, environmentService.extensionsPath]
};
this.impl = this._register(new BaseTelemetryService(config, configurationService));
} else {
this.impl = NullTelemetryService;
}
}
get isOptedIn(): boolean {
return this.impl.isOptedIn;
}
publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise<void> {
return this.impl.publicLog(eventName, data, anonymizeFilePaths);
}
getTelemetryInfo(): Promise<ITelemetryInfo> {
return this.impl.getTelemetryInfo();
}
}
registerSingleton(ITelemetryService, TelemetryService);

View File

@@ -203,7 +203,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
return this.versionId;
}
revert(soft?: boolean): Promise<void> {
async revert(soft?: boolean): Promise<void> {
if (!this.isResolved()) {
return Promise.resolve(undefined);
}
@@ -221,17 +221,18 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
loadPromise = this.load({ forceReadFromDisk: true });
}
return loadPromise.then(() => {
try {
await loadPromise;
// Emit file change event
this._onDidStateChange.fire(StateChange.REVERTED);
}, error => {
} catch (error) {
// Set flags back to previous values, we are still dirty if revert failed
undo();
return Promise.reject(error);
});
}
}
load(options?: ILoadOptions): Promise<ITextFileEditorModel> {
@@ -255,36 +256,35 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
return this.loadFromFile(options);
}
private loadFromBackup(options?: ILoadOptions): Promise<TextFileEditorModel> {
return this.backupFileService.loadBackupResource(this.resource).then(backup => {
private async loadFromBackup(options?: ILoadOptions): Promise<TextFileEditorModel> {
const backup = await this.backupFileService.loadBackupResource(this.resource);
// Make sure meanwhile someone else did not suceed or start loading
if (this.createTextEditorModelPromise || this.textEditorModel) {
return this.createTextEditorModelPromise || this;
}
// Make sure meanwhile someone else did not suceed or start loading
if (this.createTextEditorModelPromise || this.textEditorModel) {
return this.createTextEditorModelPromise || this;
}
// If we have a backup, continue loading with it
if (!!backup) {
const content: IRawTextContent = {
resource: this.resource,
name: basename(this.resource),
mtime: Date.now(),
size: 0,
etag: etag(Date.now(), 0),
value: createTextBufferFactory(''), /* will be filled later from backup */
encoding: this.fileService.encoding.getWriteEncoding(this.resource, this.preferredEncoding).encoding,
isReadonly: false
};
// If we have a backup, continue loading with it
if (!!backup) {
const content: IRawTextContent = {
resource: this.resource,
name: basename(this.resource),
mtime: Date.now(),
size: 0,
etag: etag(Date.now(), 0),
value: createTextBufferFactory(''), /* will be filled later from backup */
encoding: this.fileService.encoding.getWriteEncoding(this.resource, this.preferredEncoding).encoding,
isReadonly: false
};
return this.loadWithContent(content, options, backup);
}
return this.loadWithContent(content, options, backup);
}
// Otherwise load from file
return this.loadFromFile(options);
});
// Otherwise load from file
return this.loadFromFile(options);
}
private loadFromFile(options?: ILoadOptions): Promise<TextFileEditorModel> {
private async loadFromFile(options?: ILoadOptions): Promise<TextFileEditorModel> {
const forceReadFromDisk = options && options.forceReadFromDisk;
const allowBinary = this.isResolved() /* always allow if we resolved previously */ || (options && options.allowBinary);
@@ -305,46 +305,45 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
const currentVersionId = this.versionId;
// Resolve Content
return this.textFileService
.resolveTextContent(this.resource, { acceptTextOnly: !allowBinary, etag, encoding: this.preferredEncoding })
.then(content => {
try {
const content = await this.textFileService.resolve(this.resource, { acceptTextOnly: !allowBinary, etag, encoding: this.preferredEncoding });
// Clear orphaned state when loading was successful
this.setOrphaned(false);
// Clear orphaned state when loading was successful
this.setOrphaned(false);
// Guard against the model having changed in the meantime
if (currentVersionId === this.versionId) {
return this.loadWithContent(content, options);
}
return this;
} catch (error) {
const result = error.fileOperationResult;
// Apply orphaned state based on error code
this.setOrphaned(result === FileOperationResult.FILE_NOT_FOUND);
// NotModified status is expected and can be handled gracefully
if (result === FileOperationResult.FILE_NOT_MODIFIED_SINCE) {
// Guard against the model having changed in the meantime
if (currentVersionId === this.versionId) {
return this.loadWithContent(content, options);
this.setDirty(false); // Ensure we are not tracking a stale state
}
return this;
}, error => {
const result = error.fileOperationResult;
}
// Apply orphaned state based on error code
this.setOrphaned(result === FileOperationResult.FILE_NOT_FOUND);
// Ignore when a model has been resolved once and the file was deleted meanwhile. Since
// we already have the model loaded, we can return to this state and update the orphaned
// flag to indicate that this model has no version on disk anymore.
if (this.isResolved() && result === FileOperationResult.FILE_NOT_FOUND) {
return this;
}
// NotModified status is expected and can be handled gracefully
if (result === FileOperationResult.FILE_NOT_MODIFIED_SINCE) {
// Guard against the model having changed in the meantime
if (currentVersionId === this.versionId) {
this.setDirty(false); // Ensure we are not tracking a stale state
}
return this;
}
// Ignore when a model has been resolved once and the file was deleted meanwhile. Since
// we already have the model loaded, we can return to this state and update the orphaned
// flag to indicate that this model has no version on disk anymore.
if (this.isResolved() && result === FileOperationResult.FILE_NOT_FOUND) {
return this;
}
// Otherwise bubble up the error
return Promise.reject<TextFileEditorModel>(error);
});
// Otherwise bubble up the error
throw error;
}
}
private loadWithContent(content: IRawTextContent, options?: ILoadOptions, backup?: URI): Promise<TextFileEditorModel> {
@@ -486,12 +485,16 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
}
}
private doLoadBackup(backup: URI | undefined): Promise<ITextBufferFactory | null> {
private async doLoadBackup(backup: URI | undefined): Promise<ITextBufferFactory | null> {
if (!backup) {
return Promise.resolve(null);
return null;
}
return this.backupFileService.resolveBackupContent(backup).then(withUndefinedAsNull, error => null /* ignore errors */);
try {
return withUndefinedAsNull(await this.backupFileService.resolveBackupContent(backup));
} catch (error) {
return null; // ignore errors
}
}
protected getOrCreateMode(modeService: IModeService, preferredModeIds: string | undefined, firstLineText?: string): ILanguageSelection {
@@ -700,12 +703,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
// Save to Disk
// mark the save operation as currently pending with the versionId (it might have changed from a save participant triggering)
this.logService.trace(`doSave(${versionId}) - before updateContent()`, this.resource);
this.logService.trace(`doSave(${versionId}) - before write()`, this.resource);
const snapshot = this.createSnapshot();
if (!snapshot) {
throw new Error('Invalid snapshot');
}
return this.saveSequentializer.setPending(newVersionId, this.fileService.updateContent(this.lastResolvedDiskStat.resource, snapshot, {
return this.saveSequentializer.setPending(newVersionId, this.textFileService.write(this.lastResolvedDiskStat.resource, snapshot, {
overwriteReadonly: options.overwriteReadonly,
overwriteEncoding: options.overwriteEncoding,
mtime: this.lastResolvedDiskStat.mtime,
@@ -713,7 +716,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
etag: this.lastResolvedDiskStat.etag,
writeElevated: options.writeElevated
}).then(stat => {
this.logService.trace(`doSave(${versionId}) - after updateContent()`, this.resource);
this.logService.trace(`doSave(${versionId}) - after write()`, this.resource);
// Update dirty state unless model has changed meanwhile
if (versionId === this.versionId) {
@@ -847,7 +850,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
if (!snapshot) {
throw new Error('invalid snapshot');
}
return this.saveSequentializer.setPending(versionId, this.fileService.updateContent(this.lastResolvedDiskStat.resource, snapshot, {
return this.saveSequentializer.setPending(versionId, this.textFileService.write(this.lastResolvedDiskStat.resource, snapshot, {
mtime: this.lastResolvedDiskStat.mtime,
encoding: this.getEncoding(),
etag: this.lastResolvedDiskStat.etag

View File

@@ -121,7 +121,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
return this.mapResourceToModel.get(resource);
}
loadOrCreate(resource: URI, options?: IModelLoadOrCreateOptions): Promise<ITextFileEditorModel> {
async loadOrCreate(resource: URI, options?: IModelLoadOrCreateOptions): Promise<ITextFileEditorModel> {
// Return early if model is currently being loaded
const pendingLoad = this.mapResourceToPendingModelLoaders.get(resource);
@@ -190,7 +190,8 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
// Store pending loads to avoid race conditions
this.mapResourceToPendingModelLoaders.set(resource, modelPromise);
return modelPromise.then(model => {
try {
const model = await modelPromise;
// Make known to manager (if not already known)
this.add(resource, model);
@@ -204,7 +205,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
this.mapResourceToPendingModelLoaders.delete(resource);
return model;
}, error => {
} catch (error) {
// Free resources of this invalid model
if (model) {
@@ -214,8 +215,8 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
// Remove from pending loads
this.mapResourceToPendingModelLoaders.delete(resource);
return Promise.reject<ITextFileEditorModel>(error);
});
throw error;
}
}
getAll(resource?: URI, filter?: (model: ITextFileEditorModel) => boolean): ITextFileEditorModel[] {
@@ -315,4 +316,10 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
model.dispose();
}
dispose(): void {
super.dispose();
this.clear();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -7,8 +7,8 @@ import { URI } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IEncodingSupport, ConfirmResult, IRevertOptions } from 'vs/workbench/common/editor';
import { IResolveContentOptions, ITextSnapshot, IBaseStatWithMetadata } from 'vs/platform/files/common/files';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IResolveContentOptions, ITextSnapshot, IBaseStatWithMetadata, IWriteTextFileOptions, IFileStatWithMetadata } from 'vs/platform/files/common/files';
import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { ITextEditorModel } from 'vs/editor/common/services/resolverService';
import { ITextBufferFactory, ITextModel } from 'vs/editor/common/model';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
@@ -265,17 +265,18 @@ export interface IResolvedTextFileEditorModel extends ITextFileEditorModel {
export interface IWillMoveEvent {
oldResource: URI;
newResource: URI;
waitUntil(p: Promise<unknown>): void;
}
export interface ITextFileService extends IDisposable {
_serviceBrand: any;
_serviceBrand: ServiceIdentifier<any>;
readonly onWillMove: Event<IWillMoveEvent>;
readonly onAutoSaveConfigurationChange: Event<IAutoSaveConfiguration>;
readonly onFilesAssociationChange: Event<void>;
onWillMove: Event<IWillMoveEvent>;
readonly isHotExitEnabled: boolean;
/**
@@ -283,11 +284,6 @@ export interface ITextFileService extends IDisposable {
*/
readonly models: ITextFileEditorModelManager;
/**
* Resolve the contents of a file identified by the resource.
*/
resolveTextContent(resource: URI, options?: IResolveContentOptions): Promise<IRawTextContent>;
/**
* A resource is dirty if it has unsaved changes or is an untitled file not yet saved.
*
@@ -349,7 +345,17 @@ export interface ITextFileService extends IDisposable {
* Create a file. If the file exists it will be overwritten with the contents if
* the options enable to overwrite.
*/
create(resource: URI, contents?: string, options?: { overwrite?: boolean }): Promise<void>;
create(resource: URI, contents?: string, options?: { overwrite?: boolean }): Promise<IFileStatWithMetadata>;
/**
* Resolve the contents of a file identified by the resource.
*/
resolve(resource: URI, options?: IResolveContentOptions): Promise<IRawTextContent>;
/**
* Update a file with given contents.
*/
write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise<IFileStatWithMetadata>;
/**
* Delete a file. If the file is dirty, it will get reverted and then deleted from disk.

View File

@@ -0,0 +1,86 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { tmpdir } from 'os';
import { TextFileService } from 'vs/workbench/services/textfile/common/textFileService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { URI } from 'vs/base/common/uri';
import { ITextSnapshot, IWriteTextFileOptions, IFileStatWithMetadata } from 'vs/platform/files/common/files';
import { Schemas } from 'vs/base/common/network';
import { exists, stat, chmod, rimraf } from 'vs/base/node/pfs';
import { join, dirname } from 'vs/base/common/path';
import { isMacintosh } from 'vs/base/common/platform';
import product from 'vs/platform/product/node/product';
export class NodeTextFileService extends TextFileService {
async write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise<IFileStatWithMetadata> {
// check for overwriteReadonly property (only supported for local file://)
try {
if (options && options.overwriteReadonly && resource.scheme === Schemas.file && await exists(resource.fsPath)) {
const fileStat = await stat(resource.fsPath);
// try to change mode to writeable
await chmod(resource.fsPath, fileStat.mode | 128);
}
} catch (error) {
// ignore and simply retry the operation
}
// check for writeElevated property (only supported for local file://)
if (options && options.writeElevated && resource.scheme === Schemas.file) {
return this.writeElevated(resource, value, options);
}
return super.write(resource, value, options);
}
private async writeElevated(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise<IFileStatWithMetadata> {
// write into a tmp file first
const tmpPath = join(tmpdir(), `code-elevated-${Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 6)}`);
await this.write(URI.file(tmpPath), value, { encoding: this.fileService.encoding.getWriteEncoding(resource, options ? options.encoding : undefined).encoding });
// sudo prompt copy
await this.sudoPromptCopy(tmpPath, resource.fsPath, options);
// clean up
await rimraf(tmpPath);
return this.fileService.resolve(resource, { resolveMetadata: true });
}
private async sudoPromptCopy(source: string, target: string, options?: IWriteTextFileOptions): Promise<void> {
// load sudo-prompt module lazy
const sudoPrompt = await import('sudo-prompt');
return new Promise<void>((resolve, reject) => {
const promptOptions = {
name: this.environmentService.appNameLong.replace('-', ''),
icns: (isMacintosh && this.environmentService.isBuilt) ? join(dirname(this.environmentService.appRoot), `${product.nameShort}.icns`) : undefined
};
const sudoCommand: string[] = [`"${this.environmentService.cliPath}"`];
if (options && options.overwriteReadonly) {
sudoCommand.push('--file-chmod');
}
sudoCommand.push('--file-write', `"${source}"`, `"${target}"`);
sudoPrompt.exec(sudoCommand.join(' '), promptOptions, (error: string, stdout: string, stderr: string) => {
if (error || stderr) {
reject(error || stderr);
} else {
resolve(undefined);
}
});
});
}
}
registerSingleton(ITextFileService, NodeTextFileService);

View File

@@ -9,21 +9,22 @@ import { ITextResourcePropertiesService } from 'vs/editor/common/services/resour
import { OperatingSystem, OS } from 'vs/base/common/platform';
import { Schemas } from 'vs/base/common/network';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
export class TextResourcePropertiesService implements ITextResourcePropertiesService {
_serviceBrand: any;
_serviceBrand: ServiceIdentifier<any>;
private remoteEnvironment: IRemoteAgentEnvironment | null = null;
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@IWindowService private readonly windowService: IWindowService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IStorageService private readonly storageService: IStorageService
) {
remoteAgentService.getEnvironment().then(remoteEnv => this.remoteEnvironment = remoteEnv);
@@ -40,7 +41,8 @@ export class TextResourcePropertiesService implements ITextResourcePropertiesSer
private getOS(resource: URI): OperatingSystem {
let os = OS;
const remoteAuthority = this.windowService.getConfiguration().remoteAuthority;
const remoteAuthority = this.environmentService.configuration.remoteAuthority;
if (remoteAuthority) {
if (resource.scheme !== Schemas.file) {
const osCacheKey = `resource.authority.os.${remoteAuthority}`;
@@ -48,6 +50,7 @@ export class TextResourcePropertiesService implements ITextResourcePropertiesSer
this.storageService.store(osCacheKey, os, StorageScope.WORKSPACE);
}
}
return os;
}
}

View File

@@ -21,9 +21,8 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ColorThemeStore } from 'vs/workbench/services/themes/browser/colorThemeStore';
import { FileIconThemeStore } from 'vs/workbench/services/themes/common/fileIconThemeStore';
import { FileIconThemeData } from 'vs/workbench/services/themes/common/fileIconThemeData';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { removeClasses, addClasses } from 'vs/base/browser/dom';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IFileService, FileChangeType } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import * as resources from 'vs/base/common/resources';
@@ -100,8 +99,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
@IStorageService private readonly storageService: IStorageService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IWindowService private readonly windowService: IWindowService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IFileService private readonly fileService: IFileService
) {
@@ -218,7 +216,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
}
if (this.watchedIconThemeLocation && this.currentIconTheme && e.contains(this.watchedIconThemeLocation, FileChangeType.UPDATED)) {
await this.currentIconTheme.reload(this.fileService);
_applyIconTheme(this.currentIconTheme, () => Promise.resolve(this.currentIconTheme));
_applyIconTheme(this.currentIconTheme, () => {
this.doSetFileIconTheme(this.currentIconTheme);
return Promise.resolve(this.currentIconTheme);
});
}
});
}
@@ -243,7 +244,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
let detectHCThemeSetting = this.configurationService.getValue<boolean>(DETECT_HC_SETTING);
let colorThemeSetting: string;
if (this.windowService.getConfiguration().highContrast && detectHCThemeSetting) {
if (this.environmentService.configuration.highContrast && detectHCThemeSetting) {
colorThemeSetting = HC_THEME_ID;
} else {
colorThemeSetting = this.configurationService.getValue<string>(COLOR_THEME_SETTING);
@@ -251,15 +252,11 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
let iconThemeSetting = this.configurationService.getValue<string | null>(ICON_THEME_SETTING);
const extDevLoc = this.environmentService.extensionDevelopmentLocationURI;
const extDevLocs = this.environmentService.extensionDevelopmentLocationURI;
let uri: URI | undefined;
if (Array.isArray(extDevLoc)) {
if (extDevLocs && extDevLocs.length > 0) {
// if there are more than one ext dev paths, use first
if (extDevLoc.length > 0) {
uri = extDevLoc[0];
}
} else {
uri = extDevLoc;
uri = extDevLocs[0];
}
return Promise.all([

View File

@@ -7,7 +7,8 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { virtualMachineHint } from 'vs/base/node/id';
import * as perf from 'vs/base/common/performance';
import * as os from 'os';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
@@ -19,7 +20,6 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
/* __GDPR__FRAGMENT__
"IMemoryInfo" : {
"workingSetSize" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
@@ -312,7 +312,7 @@ class TimerService implements ITimerService {
constructor(
@IWindowsService private readonly _windowsService: IWindowsService,
@IWindowService private readonly _windowService: IWindowService,
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
@IExtensionService private readonly _extensionService: IExtensionService,
@@ -335,7 +335,7 @@ class TimerService implements ITimerService {
private async _computeStartupMetrics(): Promise<IStartupMetrics> {
const now = Date.now();
const initialStartup = !!this._windowService.getConfiguration().isInitialStartup;
const initialStartup = !!this._environmentService.configuration.isInitialStartup;
const startMark = initialStartup ? 'main:started' : 'main:loadWindow';
let totalmem: number | undefined;

View File

@@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri';
import { createDecorator, IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import * as arrays from 'vs/base/common/arrays';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { IFilesConfiguration } from 'vs/platform/files/common/files';
import { IFilesConfiguration, IFileService } from 'vs/platform/files/common/files';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Event, Emitter } from 'vs/base/common/event';
import { ResourceMap } from 'vs/base/common/map';
@@ -132,7 +132,8 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IConfigurationService private readonly configurationService: IConfigurationService
@IConfigurationService private readonly configurationService: IConfigurationService,
@IFileService private readonly fileService: IFileService
) {
super();
}
@@ -194,10 +195,10 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor
}
createOrGet(resource?: URI, modeId?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath: boolean = false): UntitledEditorInput {
if (resource) {
// Massage resource if it comes with a file:// scheme
if (resource.scheme === Schemas.file) {
// Massage resource if it comes with known file based resource
if (this.fileService.canHandleResource(resource)) {
hasAssociatedFilePath = true;
resource = resource.with({ scheme: Schemas.untitled }); // ensure we have the right scheme
}

View File

@@ -0,0 +1,187 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { IWindowService, IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IDevToolsOptions, IOpenSettings, IURIToOpen, isFolderToOpen, isWorkspaceToOpen } from 'vs/platform/windows/common/windows';
import { IRecentlyOpened } from 'vs/platform/history/common/history';
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import { ParsedArgs } from 'vs/platform/environment/common/environment';
import { URI } from 'vs/base/common/uri';
import { Disposable } from 'vs/base/common/lifecycle';
import { ILabelService } from 'vs/platform/label/common/label';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
export class WindowService extends Disposable implements IWindowService {
readonly onDidChangeFocus: Event<boolean>;
readonly onDidChangeMaximize: Event<boolean>;
_serviceBrand: any;
private _windowId: number;
private remoteAuthority: string | undefined;
private _hasFocus: boolean;
get hasFocus(): boolean { return this._hasFocus; }
constructor(
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IWindowsService private readonly windowsService: IWindowsService,
@ILabelService private readonly labelService: ILabelService
) {
super();
this._windowId = environmentService.configuration.windowId;
this.remoteAuthority = environmentService.configuration.remoteAuthority;
const onThisWindowFocus = Event.map(Event.filter(windowsService.onWindowFocus, id => id === this._windowId), _ => true);
const onThisWindowBlur = Event.map(Event.filter(windowsService.onWindowBlur, id => id === this._windowId), _ => false);
const onThisWindowMaximize = Event.map(Event.filter(windowsService.onWindowMaximize, id => id === this._windowId), _ => true);
const onThisWindowUnmaximize = Event.map(Event.filter(windowsService.onWindowUnmaximize, id => id === this._windowId), _ => false);
this.onDidChangeFocus = Event.any(onThisWindowFocus, onThisWindowBlur);
this.onDidChangeMaximize = Event.any(onThisWindowMaximize, onThisWindowUnmaximize);
this._hasFocus = document.hasFocus();
this.isFocused().then(focused => this._hasFocus = focused);
this._register(this.onDidChangeFocus(focus => this._hasFocus = focus));
}
get windowId(): number {
return this._windowId;
}
pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise<void> {
options.windowId = this.windowId;
return this.windowsService.pickFileFolderAndOpen(options);
}
pickFileAndOpen(options: INativeOpenDialogOptions): Promise<void> {
options.windowId = this.windowId;
return this.windowsService.pickFileAndOpen(options);
}
pickFolderAndOpen(options: INativeOpenDialogOptions): Promise<void> {
options.windowId = this.windowId;
return this.windowsService.pickFolderAndOpen(options);
}
pickWorkspaceAndOpen(options: INativeOpenDialogOptions): Promise<void> {
options.windowId = this.windowId;
return this.windowsService.pickWorkspaceAndOpen(options);
}
reloadWindow(args?: ParsedArgs): Promise<void> {
return this.windowsService.reloadWindow(this.windowId, args);
}
openDevTools(options?: IDevToolsOptions): Promise<void> {
return this.windowsService.openDevTools(this.windowId, options);
}
toggleDevTools(): Promise<void> {
return this.windowsService.toggleDevTools(this.windowId);
}
closeWorkspace(): Promise<void> {
return this.windowsService.closeWorkspace(this.windowId);
}
enterWorkspace(path: URI): Promise<IEnterWorkspaceResult | undefined> {
return this.windowsService.enterWorkspace(this.windowId, path);
}
openWindow(uris: IURIToOpen[], options: IOpenSettings = {}): Promise<void> {
if (!!this.remoteAuthority) {
uris.forEach(u => u.label = u.label || this.getRecentLabel(u));
}
return this.windowsService.openWindow(this.windowId, uris, options);
}
closeWindow(): Promise<void> {
return this.windowsService.closeWindow(this.windowId);
}
toggleFullScreen(): Promise<void> {
return this.windowsService.toggleFullScreen(this.windowId);
}
setRepresentedFilename(fileName: string): Promise<void> {
return this.windowsService.setRepresentedFilename(this.windowId, fileName);
}
getRecentlyOpened(): Promise<IRecentlyOpened> {
return this.windowsService.getRecentlyOpened(this.windowId);
}
focusWindow(): Promise<void> {
return this.windowsService.focusWindow(this.windowId);
}
isFocused(): Promise<boolean> {
return this.windowsService.isFocused(this.windowId);
}
isMaximized(): Promise<boolean> {
return this.windowsService.isMaximized(this.windowId);
}
maximizeWindow(): Promise<void> {
return this.windowsService.maximizeWindow(this.windowId);
}
unmaximizeWindow(): Promise<void> {
return this.windowsService.unmaximizeWindow(this.windowId);
}
minimizeWindow(): Promise<void> {
return this.windowsService.minimizeWindow(this.windowId);
}
onWindowTitleDoubleClick(): Promise<void> {
return this.windowsService.onWindowTitleDoubleClick(this.windowId);
}
setDocumentEdited(flag: boolean): Promise<void> {
return this.windowsService.setDocumentEdited(this.windowId, flag);
}
showMessageBox(options: Electron.MessageBoxOptions): Promise<IMessageBoxResult> {
return this.windowsService.showMessageBox(this.windowId, options);
}
showSaveDialog(options: Electron.SaveDialogOptions): Promise<string> {
return this.windowsService.showSaveDialog(this.windowId, options);
}
showOpenDialog(options: Electron.OpenDialogOptions): Promise<string[]> {
return this.windowsService.showOpenDialog(this.windowId, options);
}
updateTouchBar(items: ISerializableCommandAction[][]): Promise<void> {
return this.windowsService.updateTouchBar(this.windowId, items);
}
resolveProxy(url: string): Promise<string | undefined> {
return this.windowsService.resolveProxy(this.windowId, url);
}
private getRecentLabel(u: IURIToOpen): string {
if (isFolderToOpen(u)) {
return this.labelService.getWorkspaceLabel(u.folderUri, { verbose: true });
} else if (isWorkspaceToOpen(u)) {
return this.labelService.getWorkspaceLabel({ id: '', configPath: u.workspaceUri }, { verbose: true });
} else {
return this.labelService.getUriLabel(u.fileUri);
}
}
}
registerSingleton(IWindowService, WindowService);

View File

@@ -24,13 +24,14 @@ import { isLinux, isWindows, isMacintosh } from 'vs/base/common/platform';
import { isEqual, basename, isEqualOrParent, getComparisonKey } from 'vs/base/common/resources';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IFileService } from 'vs/platform/files/common/files';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
import { IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ILabelService } from 'vs/platform/label/common/label';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
export class WorkspaceEditingService implements IWorkspaceEditingService {
@@ -46,10 +47,11 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
@IBackupFileService private readonly backupFileService: IBackupFileService,
@INotificationService private readonly notificationService: INotificationService,
@ICommandService private readonly commandService: ICommandService,
@IFileService private readonly fileSystemService: IFileService,
@IFileService private readonly fileService: IFileService,
@ITextFileService private readonly textFileService: ITextFileService,
@IWindowsService private readonly windowsService: IWindowsService,
@IWorkspacesService private readonly workspaceService: IWorkspacesService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IFileDialogService private readonly fileDialogService: IFileDialogService,
@IDialogService private readonly dialogService: IDialogService,
@ILifecycleService readonly lifecycleService: ILifecycleService,
@@ -245,7 +247,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
if (path && !this.isValidTargetWorkspacePath(path)) {
return Promise.reject(null);
}
const remoteAuthority = this.windowService.getConfiguration().remoteAuthority;
const remoteAuthority = this.environmentService.configuration.remoteAuthority;
const untitledWorkspace = await this.workspaceService.createUntitledWorkspace(folders, remoteAuthority);
if (path) {
await this.saveWorkspaceAs(untitledWorkspace, path);
@@ -296,9 +298,9 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
}
// Read the contents of the workspace file, update it to new location and save it.
const raw = await this.fileSystemService.resolveContent(configPathURI);
const raw = await this.fileService.resolveContent(configPathURI);
const newRawWorkspaceContents = rewriteWorkspaceFileForNewLocation(raw.value, configPathURI, targetConfigPathURI);
await this.fileSystemService.createFile(targetConfigPathURI, newRawWorkspaceContents, { overwrite: true });
await this.textFileService.create(targetConfigPathURI, newRawWorkspaceContents, { overwrite: true });
}
private handleWorkspaceConfigurationEditingError(error: JSONEditingError): Promise<void> {
@@ -360,7 +362,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
}
}
if (this.windowService.getConfiguration().remoteAuthority) {
if (this.environmentService.configuration.remoteAuthority) {
this.windowService.reloadWindow(); // TODO aeschli: workaround until restarting works
} else {
this.extensionService.startExtensionHost();