mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-15 02:48:30 -05:00
Merge from vscode 31e03b8ffbb218a87e3941f2b63a249f061fe0e4 (#4986)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -25,7 +25,7 @@ class BroadcastService extends Disposable implements IBroadcastService {
|
||||
) {
|
||||
super();
|
||||
|
||||
this.windowId = windowService.getCurrentWindowId();
|
||||
this.windowId = windowService.windowId;
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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': [] } });
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -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[][]) => {
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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") };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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'));
|
||||
|
||||
|
||||
78
src/vs/workbench/services/files2/browser/fileService2.ts
Normal file
78
src/vs/workbench/services/files2/browser/fileService2.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
18
src/vs/workbench/services/remote/node/tunnelService.ts
Normal file
18
src/vs/workbench/services/remote/node/tunnelService.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
86
src/vs/workbench/services/textfile/node/textFileService.ts
Normal file
86
src/vs/workbench/services/textfile/node/textFileService.ts
Normal 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);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user