Merge from vscode 966b87dd4013be1a9c06e2b8334522ec61905cc2 (#4696)

This commit is contained in:
Anthony Dresser
2019-03-26 11:43:38 -07:00
committed by GitHub
parent b1393ae615
commit 0d8ef9583b
268 changed files with 5947 additions and 3422 deletions

View File

@@ -15,7 +15,7 @@ import { isResourceFileEdit, isResourceTextEdit, ResourceFileEdit, ResourceTextE
import { IModelService } from 'vs/editor/common/services/modelService';
import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
import { localize } from 'vs/nls';
import { IFileService } from 'vs/platform/files/common/files';
import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ILogService } from 'vs/platform/log/common/log';
import { emptyProgressRunner, IProgress, IProgressRunner } from 'vs/platform/progress/common/progress';
@@ -326,7 +326,12 @@ export class BulkEdit {
} else if (!edit.newUri && edit.oldUri) {
// delete file
if (!options.ignoreIfNotExists || await this._fileService.existsFile(edit.oldUri)) {
await this._textFileService.delete(edit.oldUri, { useTrash: this._configurationService.getValue<boolean>('files.enableTrash'), recursive: options.recursive });
let useTrash = this._configurationService.getValue<boolean>('files.enableTrash');
if (useTrash && !(await this._fileService.hasCapability(edit.oldUri, FileSystemProviderCapabilities.Trash))) {
useTrash = false; // not supported by provider
}
await this._textFileService.delete(edit.oldUri, { useTrash, recursive: options.recursive });
}
} else if (edit.newUri && !edit.oldUri) {

View File

@@ -28,6 +28,7 @@ import { ITextModel } from 'vs/editor/common/model';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { withUndefinedAsNull, withNullAsUndefined } from 'vs/base/common/types';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
export const enum ConfigurationEditingErrorCode {
@@ -120,6 +121,7 @@ export class ConfigurationEditingService {
public _serviceBrand: any;
private queue: Queue<void>;
private remoteSettingsResource: URI | null;
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
@@ -130,9 +132,15 @@ export class ConfigurationEditingService {
@ITextFileService private readonly textFileService: ITextFileService,
@INotificationService private readonly notificationService: INotificationService,
@IPreferencesService private readonly preferencesService: IPreferencesService,
@IEditorService private readonly editorService: IEditorService
@IEditorService private readonly editorService: IEditorService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService
) {
this.queue = new Queue<void>();
remoteAgentService.getEnvironment().then(environment => {
if (environment) {
this.remoteSettingsResource = environment.appSettingsPath;
}
});
}
writeConfiguration(target: ConfigurationTarget, value: IConfigurationValue, options: IConfigurationEditingOptions = {}): Promise<void> {
@@ -458,7 +466,7 @@ export class ConfigurationEditingService {
if (config.key) {
const standaloneConfigurationKeys = Object.keys(WORKSPACE_STANDALONE_CONFIGURATIONS);
for (const key of standaloneConfigurationKeys) {
const resource = this.getConfigurationFileResource(target, WORKSPACE_STANDALONE_CONFIGURATIONS[key], overrides.resource);
const resource = this.getConfigurationFileResource(target, config, WORKSPACE_STANDALONE_CONFIGURATIONS[key], overrides.resource);
// Check for prefix
if (config.key === key) {
@@ -478,10 +486,10 @@ export class ConfigurationEditingService {
let key = config.key;
let jsonPath = overrides.overrideIdentifier ? [keyFromOverrideIdentifier(overrides.overrideIdentifier), key] : [key];
if (target === ConfigurationTarget.USER) {
return { key, jsonPath, value: config.value, resource: URI.file(this.environmentService.appSettingsPath), target };
return { key, jsonPath, value: config.value, resource: withNullAsUndefined(this.getConfigurationFileResource(target, config, '', null)), target };
}
const resource = this.getConfigurationFileResource(target, FOLDER_SETTINGS_PATH, overrides.resource);
const resource = this.getConfigurationFileResource(target, config, FOLDER_SETTINGS_PATH, overrides.resource);
if (this.isWorkspaceConfigurationResource(resource)) {
jsonPath = ['settings', ...jsonPath];
}
@@ -493,8 +501,17 @@ export class ConfigurationEditingService {
return !!(workspace.configuration && resource && workspace.configuration.fsPath === resource.fsPath);
}
private getConfigurationFileResource(target: ConfigurationTarget, relativePath: string, resource: URI | null | undefined): URI | null {
private getConfigurationFileResource(target: ConfigurationTarget, config: IConfigurationValue, relativePath: string, resource: URI | null | undefined): URI | null {
if (target === ConfigurationTarget.USER_LOCAL) {
return URI.file(this.environmentService.appSettingsPath);
}
if (target === ConfigurationTarget.USER_REMOTE) {
return this.remoteSettingsResource;
}
if (target === ConfigurationTarget.USER) {
if (this.configurationService.inspect(config.key).userRemote !== undefined) {
return this.remoteSettingsResource;
}
return URI.file(this.environmentService.appSettingsPath);
}

View File

@@ -131,13 +131,14 @@ export class Configuration extends BaseConfiguration {
constructor(
defaults: ConfigurationModel,
user: ConfigurationModel,
localUser: ConfigurationModel,
remoteUser: ConfigurationModel,
workspaceConfiguration: ConfigurationModel,
folders: ResourceMap<ConfigurationModel>,
memoryConfiguration: ConfigurationModel,
memoryConfigurationByResource: ResourceMap<ConfigurationModel>,
private readonly _workspace?: Workspace) {
super(defaults, user, workspaceConfiguration, folders, memoryConfiguration, memoryConfigurationByResource);
super(defaults, localUser, remoteUser, workspaceConfiguration, folders, memoryConfiguration, memoryConfigurationByResource);
}
getValue(key: string | undefined, overrides: IConfigurationOverrides = {}): any {
@@ -164,17 +165,26 @@ export class Configuration extends BaseConfiguration {
return super.keys(this._workspace);
}
compareAndUpdateUserConfiguration(user: ConfigurationModel): ConfigurationChangeEvent {
const { added, updated, removed } = compare(this.user, user);
compareAndUpdateLocalUserConfiguration(user: ConfigurationModel): ConfigurationChangeEvent {
const { added, updated, removed } = compare(this.localUserConfiguration, user);
let changedKeys = [...added, ...updated, ...removed];
if (changedKeys.length) {
super.updateUserConfiguration(user);
super.updateLocalUserConfiguration(user);
}
return new ConfigurationChangeEvent().change(changedKeys);
}
compareAndUpdateRemoteUserConfiguration(user: ConfigurationModel): ConfigurationChangeEvent {
const { added, updated, removed } = compare(this.remoteUserConfiguration, user);
let changedKeys = [...added, ...updated, ...removed];
if (changedKeys.length) {
super.updateRemoteUserConfiguration(user);
}
return new ConfigurationChangeEvent().change(changedKeys);
}
compareAndUpdateWorkspaceConfiguration(workspaceConfiguration: ConfigurationModel): ConfigurationChangeEvent {
const { added, updated, removed } = compare(this.workspace, workspaceConfiguration);
const { added, updated, removed } = compare(this.workspaceConfiguration, workspaceConfiguration);
let changedKeys = [...added, ...updated, ...removed];
if (changedKeys.length) {
super.updateWorkspaceConfiguration(workspaceConfiguration);
@@ -183,7 +193,7 @@ export class Configuration extends BaseConfiguration {
}
compareAndUpdateFolderConfiguration(resource: URI, folderConfiguration: ConfigurationModel): ConfigurationChangeEvent {
const currentFolderConfiguration = this.folders.get(resource);
const currentFolderConfiguration = this.folderConfigurations.get(resource);
if (currentFolderConfiguration) {
const { added, updated, removed } = compare(currentFolderConfiguration, folderConfiguration);
let changedKeys = [...added, ...updated, ...removed];
@@ -202,7 +212,7 @@ export class Configuration extends BaseConfiguration {
// Do not remove workspace configuration
return new ConfigurationChangeEvent();
}
const folderConfig = this.folders.get(folder);
const folderConfig = this.folderConfigurations.get(folder);
if (!folderConfig) {
throw new Error('Unknown folder');
}

View File

@@ -25,7 +25,145 @@ import { extname, join } from 'vs/base/common/path';
import { equals } from 'vs/base/common/objects';
import { Schemas } from 'vs/base/common/network';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IConfigurationModel } from 'vs/platform/configuration/common/configuration';
import { IConfigurationModel, compare } from 'vs/platform/configuration/common/configuration';
import { FileServiceBasedUserConfiguration, NodeBasedUserConfiguration } from 'vs/platform/configuration/node/configuration';
export class LocalUserConfiguration extends Disposable {
private userConfiguration: NodeBasedUserConfiguration | FileServiceBasedUserConfiguration;
private readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());
public readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;
constructor(
environmentService: IEnvironmentService
) {
super();
this.userConfiguration = this._register(new NodeBasedUserConfiguration(environmentService.appSettingsPath));
this._register(this.userConfiguration.onDidChangeConfiguration(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)));
}
initialize(): Promise<ConfigurationModel> {
return this.userConfiguration.initialize();
}
reload(): Promise<ConfigurationModel> {
return this.userConfiguration.reload();
}
async adopt(fileService: IFileService): Promise<ConfigurationModel | null> {
return null;
}
}
export class RemoteUserConfiguration extends Disposable {
private readonly _cachedConfiguration: CachedUserConfiguration;
private _userConfiguration: FileServiceBasedUserConfiguration | CachedUserConfiguration;
private readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());
public readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;
constructor(
remoteAuthority: string,
environmentService: IEnvironmentService
) {
super();
this._userConfiguration = this._cachedConfiguration = new CachedUserConfiguration(remoteAuthority, environmentService);
}
initialize(): Promise<ConfigurationModel> {
return this._userConfiguration.initialize();
}
reload(): Promise<ConfigurationModel> {
return this._userConfiguration.reload();
}
async adopt(configurationResource: URI | null, fileService: IFileService): Promise<ConfigurationModel | null> {
if (this._userConfiguration instanceof CachedUserConfiguration) {
const oldConfigurationModel = this._userConfiguration.getConfigurationModel();
let newConfigurationModel = new ConfigurationModel();
if (configurationResource) {
this._userConfiguration = new FileServiceBasedUserConfiguration(configurationResource, fileService);
this._register(this._userConfiguration.onDidChangeConfiguration(configurationModel => this.onDidUserConfigurationChange(configurationModel)));
newConfigurationModel = await this._userConfiguration.initialize();
}
const { added, updated, removed } = compare(oldConfigurationModel, newConfigurationModel);
if (added.length > 0 || updated.length > 0 || removed.length > 0) {
this.updateCache(newConfigurationModel);
return newConfigurationModel;
}
}
return null;
}
private onDidUserConfigurationChange(configurationModel: ConfigurationModel): void {
this.updateCache(configurationModel);
this._onDidChangeConfiguration.fire(configurationModel);
}
private updateCache(configurationModel: ConfigurationModel): Promise<void> {
return this._cachedConfiguration.updateConfiguration(configurationModel);
}
}
class CachedUserConfiguration extends Disposable {
private readonly _onDidChange: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());
readonly onDidChange: Event<ConfigurationModel> = this._onDidChange.event;
private readonly cachedFolderPath: string;
private readonly cachedConfigurationPath: string;
private configurationModel: ConfigurationModel;
constructor(
remoteAuthority: string,
private environmentService: IEnvironmentService
) {
super();
this.cachedFolderPath = join(this.environmentService.userDataPath, 'CachedConfigurations', 'user', remoteAuthority);
this.cachedConfigurationPath = join(this.cachedFolderPath, 'configuration.json');
this.configurationModel = new ConfigurationModel();
}
getConfigurationModel(): ConfigurationModel {
return this.configurationModel;
}
initialize(): Promise<ConfigurationModel> {
return this.reload();
}
reload(): Promise<ConfigurationModel> {
return pfs.readFile(this.cachedConfigurationPath)
.then(content => content.toString(), () => '')
.then(content => {
try {
const parsed: IConfigurationModel = JSON.parse(content);
this.configurationModel = new ConfigurationModel(parsed.contents, parsed.keys, parsed.overrides);
} catch (e) {
}
return this.configurationModel;
});
}
updateConfiguration(configurationModel: ConfigurationModel): Promise<void> {
const raw = JSON.stringify(configurationModel.toJSON());
return this.createCachedFolder().then(created => {
if (created) {
return configurationModel.keys.length ? pfs.writeFile(this.cachedConfigurationPath, raw) : pfs.rimraf(this.cachedFolderPath);
}
return undefined;
});
}
private createCachedFolder(): Promise<boolean> {
return Promise.resolve(pfs.exists(this.cachedFolderPath))
.then(undefined, () => false)
.then(exists => exists ? exists : pfs.mkdirp(this.cachedFolderPath).then(() => true, () => false));
}
}
export interface IWorkspaceIdentifier {
id: string;

View File

@@ -28,14 +28,15 @@ import { ICommandService } from 'vs/platform/commands/common/commands';
import product from 'vs/platform/product/node/product';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ConfigurationEditingService } from 'vs/workbench/services/configuration/common/configurationEditingService';
import { WorkspaceConfiguration, FolderConfiguration } from 'vs/workbench/services/configuration/node/configuration';
import { WorkspaceConfiguration, FolderConfiguration, RemoteUserConfiguration, LocalUserConfiguration } from 'vs/workbench/services/configuration/node/configuration';
import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService';
import { UserConfiguration } from 'vs/platform/configuration/node/configuration';
import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
import { localize } from 'vs/nls';
import { isEqual, dirname } from 'vs/base/common/resources';
import { mark } from 'vs/base/common/performance';
import { Schemas } from 'vs/base/common/network';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
export class WorkspaceService extends Disposable implements IConfigurationService, IWorkspaceContextService {
@@ -45,7 +46,8 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
private completeWorkspaceBarrier: Barrier;
private _configuration: Configuration;
private defaultConfiguration: DefaultConfigurationModel;
private userConfiguration: UserConfiguration;
private localUserConfiguration: LocalUserConfiguration;
private remoteUserConfiguration: RemoteUserConfiguration | null = null;
private workspaceConfiguration: WorkspaceConfiguration;
private cachedFolderConfigs: ResourceMap<FolderConfiguration>;
@@ -67,14 +69,18 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
private configurationEditingService: ConfigurationEditingService;
private jsonEditingService: JSONEditingService;
constructor(private environmentService: IEnvironmentService, private workspaceSettingsRootFolder: string = FOLDER_CONFIG_FOLDER_NAME) {
constructor(configuration: IWindowConfiguration, private environmentService: IEnvironmentService, private remoteAgentService: IRemoteAgentService, private workspaceSettingsRootFolder: string = FOLDER_CONFIG_FOLDER_NAME) {
super();
this.completeWorkspaceBarrier = new Barrier();
this.defaultConfiguration = new DefaultConfigurationModel();
this.userConfiguration = this._register(new UserConfiguration(environmentService.appSettingsPath));
this.localUserConfiguration = this._register(new LocalUserConfiguration(environmentService));
this._register(this.localUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onLocalUserConfigurationChanged(userConfiguration)));
if (configuration.remoteAuthority) {
this.remoteUserConfiguration = this._register(new RemoteUserConfiguration(configuration.remoteAuthority, environmentService));
this._register(this.remoteUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onRemoteUserConfigurationChanged(userConfiguration)));
}
this.workspaceConfiguration = this._register(new WorkspaceConfiguration(environmentService));
this._register(this.userConfiguration.onDidChangeConfiguration(userConfiguration => this.onUserConfigurationChanged(userConfiguration)));
this._register(this.workspaceConfiguration.onDidUpdateConfiguration(() => this.onWorkspaceConfigurationChanged()));
this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidSchemaChange(e => this.registerConfigurationSchemas()));
@@ -254,8 +260,8 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
return this.reloadWorkspaceFolderConfiguration(folder, key);
}
return this.reloadUserConfiguration()
.then(userConfigurationModel => this.reloadWorkspaceConfiguration()
.then(() => this.loadConfiguration(userConfigurationModel)));
.then(({ local, remote }) => this.reloadWorkspaceConfiguration()
.then(() => this.loadConfiguration(local, remote)));
}
inspect<T>(key: string, overrides?: IConfigurationOverrides): {
@@ -289,6 +295,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
acquireFileService(fileService: IFileService): void {
this.fileService = fileService;
const changedWorkspaceFolders: IWorkspaceFolder[] = [];
this.localUserConfiguration.adopt(fileService);
Promise.all([this.workspaceConfiguration.adopt(fileService), ...this.cachedFolderConfigs.values()
.map(folderConfiguration => folderConfiguration.adopt(fileService)
.then(result => {
@@ -306,6 +313,15 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
}
this.releaseWorkspaceBarrier();
});
if (this.remoteUserConfiguration) {
this.remoteAgentService.getEnvironment()
.then(environment => this.remoteUserConfiguration!.adopt(environment ? environment.appSettingsPath : null, fileService)
.then(changedModel => {
if (changedModel) {
this.onRemoteUserConfigurationChanged(changedModel);
}
}));
}
}
acquireInstantiationService(instantiationService: IInstantiationService): void {
@@ -425,12 +441,18 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
private initializeConfiguration(): Promise<void> {
this.registerConfigurationSchemas();
return this.userConfiguration.initialize()
.then(userConfigurationModel => this.loadConfiguration(userConfigurationModel));
return this.initializeUserConfiguration()
.then(({ local, remote }) => this.loadConfiguration(local, remote));
}
private reloadUserConfiguration(key?: string): Promise<ConfigurationModel> {
return this.userConfiguration.reload();
private initializeUserConfiguration(): Promise<{ local: ConfigurationModel, remote: ConfigurationModel }> {
return Promise.all([this.localUserConfiguration.initialize(), this.remoteUserConfiguration ? this.remoteUserConfiguration.initialize() : Promise.resolve(new ConfigurationModel())])
.then(([local, remote]) => ({ local, remote }));
}
private reloadUserConfiguration(key?: string): Promise<{ local: ConfigurationModel, remote: ConfigurationModel }> {
return Promise.all([this.localUserConfiguration.reload(), this.remoteUserConfiguration ? this.remoteUserConfiguration.reload() : Promise.resolve(new ConfigurationModel())])
.then(([local, remote]) => ({ local, remote }));
}
private reloadWorkspaceConfiguration(key?: string): Promise<void> {
@@ -448,7 +470,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
return this.onWorkspaceFolderConfigurationChanged(folder, key);
}
private loadConfiguration(userConfigurationModel: ConfigurationModel): Promise<void> {
private loadConfiguration(userConfigurationModel: ConfigurationModel, remoteUserConfigurationModel: ConfigurationModel): Promise<void> {
// reset caches
this.cachedFolderConfigs = new ResourceMap<FolderConfiguration>();
@@ -461,7 +483,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration));
const currentConfiguration = this._configuration;
this._configuration = new Configuration(this.defaultConfiguration, userConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), this.workspace);
this._configuration = new Configuration(this.defaultConfiguration, userConfigurationModel, remoteUserConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), this.workspace);
if (currentConfiguration) {
const changedKeys = this._configuration.compare(currentConfiguration);
@@ -528,8 +550,13 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
}
}
private onUserConfigurationChanged(userConfiguration: ConfigurationModel): void {
const keys = this._configuration.compareAndUpdateUserConfiguration(userConfiguration);
private onLocalUserConfigurationChanged(userConfiguration: ConfigurationModel): void {
const keys = this._configuration.compareAndUpdateLocalUserConfiguration(userConfiguration);
this.triggerConfigurationChange(keys, ConfigurationTarget.USER);
}
private onRemoteUserConfigurationChanged(userConfiguration: ConfigurationModel): void {
const keys = this._configuration.compareAndUpdateRemoteUserConfiguration(userConfiguration);
this.triggerConfigurationChange(keys, ConfigurationTarget.USER);
}
@@ -618,7 +645,13 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
.then(() => {
switch (target) {
case ConfigurationTarget.USER:
return this.reloadUserConfiguration().then(_ => Promise.resolve());
return this.reloadUserConfiguration()
.then(({ local, remote }) => {
this.onLocalUserConfigurationChanged(local);
if (this.remoteUserConfiguration) {
this.onRemoteUserConfigurationChanged(remote);
}
});
case ConfigurationTarget.WORKSPACE:
return this.reloadWorkspaceConfiguration();
case ConfigurationTarget.WORKSPACE_FOLDER:
@@ -670,9 +703,9 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
case ConfigurationTarget.DEFAULT:
return this._configuration.defaults.contents;
case ConfigurationTarget.USER:
return this._configuration.user.contents;
return this._configuration.userConfiguration.contents;
case ConfigurationTarget.WORKSPACE:
return this._configuration.workspace.contents;
return this._configuration.workspaceConfiguration.contents;
}
return {};
}

View File

@@ -201,7 +201,7 @@ suite('AllKeysConfigurationChangeEvent', () => {
test('changeEvent affects keys for any resource', () => {
const configuraiton = new Configuration(new ConfigurationModel({}, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']),
new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), null!);
new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), null!);
let testObject = new AllKeysConfigurationChangeEvent(configuraiton, ConfigurationTarget.USER, null);
assert.deepEqual(testObject.affectedKeys, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']);

View File

@@ -36,6 +36,9 @@ import { ICommandService } from 'vs/platform/commands/common/commands';
import { CommandService } from 'vs/workbench/services/commands/common/commandService';
import { URI } from 'vs/base/common/uri';
import { createHash } from 'crypto';
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
class SettingsTestEnvironmentService extends EnvironmentService {
@@ -100,7 +103,9 @@ suite('ConfigurationEditingService', () => {
instantiationService = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile);
instantiationService.stub(IEnvironmentService, environmentService);
const workspaceService = new WorkspaceService(environmentService);
const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {});
instantiationService.stub(IRemoteAgentService, remoteAgentService);
const workspaceService = new WorkspaceService(<IWindowConfiguration>{}, environmentService, remoteAgentService);
instantiationService.stub(IWorkspaceContextService, workspaceService);
return workspaceService.initialize(noWorkspace ? { id: '' } : { folder: URI.file(workspaceDir), id: createHash('md5').update(URI.file(workspaceDir).toString()).digest('hex') }).then(() => {
instantiationService.stub(IConfigurationService, workspaceService);

View File

@@ -37,6 +37,10 @@ import { Schemas } from 'vs/base/common/network';
import { originalFSPath } from 'vs/base/common/resources';
import { isLinux } from 'vs/base/common/platform';
import { IWorkspaceIdentifier } from 'vs/workbench/services/configuration/node/configuration';
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl';
import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-browser/remoteAuthorityResolverService';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
class SettingsTestEnvironmentService extends EnvironmentService {

View File

@@ -47,6 +47,7 @@ export class RemoteFileDialog {
private userValue: string;
private scheme: string = REMOTE_HOST_SCHEME;
private shouldOverwriteFile: boolean = false;
private autoComplete: string;
constructor(
@IFileService private readonly fileService: IFileService,
@@ -222,18 +223,21 @@ export class RemoteFileDialog {
});
this.filePickBox.onDidChangeValue(async value => {
if (value !== this.userValue) {
this.filePickBox.validationMessage = undefined;
this.shouldOverwriteFile = false;
const trimmedPickBoxValue = ((this.filePickBox.value.length > 1) && this.endsWithSlash(this.filePickBox.value)) ? this.filePickBox.value.substr(0, this.filePickBox.value.length - 1) : this.filePickBox.value;
const valueUri = this.remoteUriFrom(trimmedPickBoxValue);
if (!resources.isEqual(this.currentFolder, valueUri, true)) {
await this.tryUpdateItems(value, this.remoteUriFrom(this.filePickBox.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) {
this.filePickBox.validationMessage = undefined;
this.shouldOverwriteFile = false;
const trimmedPickBoxValue = ((this.filePickBox.value.length > 1) && this.endsWithSlash(this.filePickBox.value)) ? this.filePickBox.value.substr(0, this.filePickBox.value.length - 1) : this.filePickBox.value;
const valueUri = this.remoteUriFrom(trimmedPickBoxValue);
if (!resources.isEqual(this.currentFolder, valueUri, true)) {
await this.tryUpdateItems(value, this.remoteUriFrom(this.filePickBox.value));
}
this.setActiveItems(value);
this.userValue = value;
} else {
this.filePickBox.activeItems = [];
}
this.setActiveItems(value);
this.userValue = value;
} else {
this.filePickBox.activeItems = [];
}
});
this.filePickBox.onDidHide(() => {
@@ -356,8 +360,12 @@ export class RemoteFileDialog {
const itemBasename = (item.label === '..') ? item.label : resources.basename(item.uri);
if ((itemBasename.length >= inputBasename.length) && (itemBasename.substr(0, inputBasename.length).toLowerCase() === inputBasename.toLowerCase())) {
this.filePickBox.activeItems = [item];
this.filePickBox.value = this.filePickBox.value + itemBasename.substr(inputBasename.length);
this.filePickBox.valueSelection = [value.length, this.filePickBox.value.length];
const insertValue = itemBasename.substr(inputBasename.length);
this.autoComplete = value + insertValue;
if (this.filePickBox.inputHasFocus()) {
document.execCommand('insertText', false, insertValue);
this.filePickBox.valueSelection = [value.length, this.filePickBox.value.length];
}
hasMatch = true;
break;
}
@@ -520,7 +528,7 @@ export class RemoteFileDialog {
}
if (this.fallbackListItem) {
sorted.unshift(this.fallbackListItem);
sorted.push(this.fallbackListItem);
}
return sorted;
}

View File

@@ -10,10 +10,14 @@ import { isLinux, isWindows } from 'vs/base/common/platform';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
import { IDialogService, IConfirmation, IConfirmationResult, IDialogOptions } from 'vs/platform/dialogs/common/dialogs';
import { DialogService as HTMLDialogService } from 'vs/platform/dialogs/browser/dialogService';
import { ILogService } from 'vs/platform/log/common/log';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { DialogChannel } from 'vs/platform/dialogs/node/dialogIpc';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { IThemeService } from 'vs/platform/theme/common/themeService';
interface IMassagedMessageBoxOptions {
@@ -31,6 +35,38 @@ interface IMassagedMessageBoxOptions {
}
export class DialogService implements IDialogService {
_serviceBrand: any;
private impl: IDialogService;
constructor(
@IConfigurationService configurationService: IConfigurationService,
@ILogService logService: ILogService,
@ILayoutService layoutService: ILayoutService,
@IThemeService themeService: IThemeService,
@IWindowService windowService: IWindowService,
@ISharedProcessService sharedProcessService: ISharedProcessService
) {
// Use HTML based dialogs
if (configurationService.getValue('workbench.dialogs.customEnabled') === true) {
this.impl = new HTMLDialogService(logService, layoutService, themeService);
}
// Electron dialog service
else {
this.impl = new NativeDialogService(windowService, logService, sharedProcessService);
}
}
confirm(confirmation: IConfirmation): Promise<IConfirmationResult> {
return this.impl.confirm(confirmation);
}
show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions | undefined): Promise<number> {
return this.impl.show(severity, message, buttons, options);
}
}
class NativeDialogService implements IDialogService {
_serviceBrand: any;

View File

@@ -18,8 +18,9 @@ import { isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { IRemoteConsoleLog, log, parse } from 'vs/base/common/console';
import { findFreePort, randomPort } from 'vs/base/node/ports';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
import { PersistentProtocol, generateRandomPipeName } from 'vs/base/parts/ipc/node/ipc.net';
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 { IBroadcast, IBroadcastService } from 'vs/workbench/services/broadcast/common/broadcast';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { EXTENSION_ATTACH_BROADCAST_CHANNEL, EXTENSION_CLOSE_EXTHOST_BROADCAST_CHANNEL, EXTENSION_LOG_BROADCAST_CHANNEL, EXTENSION_RELOAD_BROADCAST_CHANNEL, EXTENSION_TERMINATE_BROADCAST_CHANNEL } from 'vs/platform/extensions/common/extensionHost';
@@ -36,6 +37,7 @@ import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/
import { withNullAsUndefined } from 'vs/base/common/types';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { parseExtensionDevOptions } from '../common/extensionDevOptions';
import { VSBuffer } from 'vs/base/common/buffer';
export interface IExtensionHostStarter {
readonly onCrashed: Event<[number, string | null]>;
@@ -344,7 +346,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
// using a buffered message protocol here because between now
// and the first time a `then` executes some messages might be lost
// unless we immediately register a listener for `onMessage`.
resolve(new PersistentProtocol(this._extensionHostConnection));
resolve(new PersistentProtocol(new NodeSocket(this._extensionHostConnection)));
});
}).then((protocol) => {
@@ -377,7 +379,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
// Wait 60s for the initialized message
installTimeoutCheck();
protocol.send(Buffer.from(JSON.stringify(data)));
protocol.send(VSBuffer.fromString(JSON.stringify(data)));
});
return;
}

View File

@@ -7,7 +7,7 @@ import * as errors from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import * as strings from 'vs/base/common/strings';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ExtHostCustomersRegistry } from 'vs/workbench/api/common/extHostCustomers';
@@ -320,7 +320,7 @@ class RPCLogger implements IRPCProtocolLogger {
private _totalIncoming = 0;
private _totalOutgoing = 0;
private _log(direction: string, totalLength, msgLength: number, req: number, initiator: RequestInitiator, str: string, data: any): void {
private _log(direction: string, totalLength: number, msgLength: number, req: number, initiator: RequestInitiator, str: string, data: any): void {
data = pretty(data);
const colorTable = colorTables[initiator];

View File

@@ -9,7 +9,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
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/node/ipc';
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 { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';

View File

@@ -7,12 +7,14 @@ import * as nativeWatchdog from 'native-watchdog';
import * as net from 'net';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Event } from 'vs/base/common/event';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
import { PersistentProtocol, ProtocolConstants } from 'vs/base/parts/ipc/node/ipc.net';
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 { VSBuffer } from 'vs/base/common/buffer';
// 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
@@ -58,18 +60,18 @@ function _createExtHostProtocol(): Promise<IMessagePassingProtocol> {
process.on('message', (msg: IExtHostSocketMessage, handle: net.Socket) => {
if (msg && msg.type === 'VSCODE_EXTHOST_IPC_SOCKET') {
const initialDataChunk = Buffer.from(msg.initialDataChunk, 'base64');
const initialDataChunk = VSBuffer.wrap(Buffer.from(msg.initialDataChunk, 'base64'));
if (protocol) {
// reconnection case
if (disconnectWaitTimer) {
clearTimeout(disconnectWaitTimer);
disconnectWaitTimer = null;
}
protocol.beginAcceptReconnection(handle, initialDataChunk);
protocol.beginAcceptReconnection(new NodeSocket(handle), initialDataChunk);
protocol.endAcceptReconnection();
} else {
clearTimeout(timer);
protocol = new PersistentProtocol(handle, initialDataChunk);
protocol = new PersistentProtocol(new NodeSocket(handle), initialDataChunk);
protocol.onClose(() => onTerminate());
resolve(protocol);
@@ -99,7 +101,7 @@ function _createExtHostProtocol(): Promise<IMessagePassingProtocol> {
const socket = net.createConnection(pipeName, () => {
socket.removeListener('error', reject);
resolve(new PersistentProtocol(socket));
resolve(new PersistentProtocol(new NodeSocket(socket)));
});
socket.once('error', reject);

View File

@@ -3,6 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VSBuffer } from 'vs/base/common/buffer';
export interface IExtHostReadyMessage {
type: 'VSCODE_EXTHOST_IPC_READY';
}
@@ -18,24 +20,24 @@ export const enum MessageType {
Terminate
}
export function createMessageOfType(type: MessageType): Buffer {
const result = Buffer.allocUnsafe(1);
export function createMessageOfType(type: MessageType): VSBuffer {
const result = VSBuffer.alloc(1);
switch (type) {
case MessageType.Initialized: result.writeUInt8(1, 0); break;
case MessageType.Ready: result.writeUInt8(2, 0); break;
case MessageType.Terminate: result.writeUInt8(3, 0); break;
case MessageType.Initialized: result.writeUint8(1, 0); break;
case MessageType.Ready: result.writeUint8(2, 0); break;
case MessageType.Terminate: result.writeUint8(3, 0); break;
}
return result;
}
export function isMessageOfType(message: Buffer, type: MessageType): boolean {
if (message.length !== 1) {
export function isMessageOfType(message: VSBuffer, type: MessageType): boolean {
if (message.byteLength !== 1) {
return false;
}
switch (message.readUInt8(0)) {
switch (message.readUint8(0)) {
case 1: return type === MessageType.Initialized;
case 2: return type === MessageType.Ready;
case 3: return type === MessageType.Terminate;

View File

@@ -457,7 +457,7 @@ async function readCaCertificates() {
}
function readWindowsCaCertificates() {
const winCA = require.__$__nodeRequire<any>('win-ca-lib');
const winCA = require.__$__nodeRequire<any>('vscode-windows-ca-certs');
let ders: any[] = [];
const store = winCA();
@@ -515,7 +515,7 @@ async function readLinuxCaCertificates() {
return undefined;
}
function derToPem(blob) {
function derToPem(blob: Buffer) {
const lines = ['-----BEGIN CERTIFICATE-----'];
const der = blob.toString('base64');
for (let i = 0; i < der.length; i += 64) {

View File

@@ -10,9 +10,10 @@ import * as errors from 'vs/base/common/errors';
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/node/ipc';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { LazyPromise } from 'vs/workbench/services/extensions/node/lazyPromise';
import { IRPCProtocol, ProxyIdentifier, getStringIdentifierForProxy } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import { VSBuffer } from 'vs/base/common/buffer';
export interface JSONStringifyReplacer {
(key: string, value: any): any;
@@ -205,12 +206,12 @@ export class RPCProtocol extends Disposable implements IRPCProtocol {
}
}
private _receiveOneMessage(rawmsg: Buffer): void {
private _receiveOneMessage(rawmsg: VSBuffer): void {
if (this._isDisposed) {
return;
}
const msgLength = rawmsg.length;
const msgLength = rawmsg.byteLength;
const buff = MessageBuffer.read(rawmsg, 0);
const messageType = <MessageType>buff.readUInt8();
const req = buff.readUInt32();
@@ -262,6 +263,11 @@ export class RPCProtocol extends Disposable implements IRPCProtocol {
this._receiveReply(msgLength, req, value);
break;
}
case MessageType.ReplyOKVSBuffer: {
let value = MessageIO.deserializeReplyOKVSBuffer(buff);
this._receiveReply(msgLength, req, value);
break;
}
case MessageType.ReplyErrError: {
let err = MessageIO.deserializeReplyErrError(buff);
if (this._uriTransformer) {
@@ -435,24 +441,24 @@ export class RPCProtocol extends Disposable implements IRPCProtocol {
class MessageBuffer {
public static alloc(type: MessageType, req: number, messageSize: number): MessageBuffer {
let result = new MessageBuffer(Buffer.allocUnsafe(messageSize + 1 /* type */ + 4 /* req */), 0);
let result = new MessageBuffer(VSBuffer.alloc(messageSize + 1 /* type */ + 4 /* req */), 0);
result.writeUInt8(type);
result.writeUInt32(req);
return result;
}
public static read(buff: Buffer, offset: number): MessageBuffer {
public static read(buff: VSBuffer, offset: number): MessageBuffer {
return new MessageBuffer(buff, offset);
}
private _buff: Buffer;
private _buff: VSBuffer;
private _offset: number;
public get buffer(): Buffer {
public get buffer(): VSBuffer {
return this._buff;
}
private constructor(buff: Buffer, offset: number) {
private constructor(buff: VSBuffer, offset: number) {
this._buff = buff;
this._offset = offset;
}
@@ -462,108 +468,136 @@ class MessageBuffer {
}
public writeUInt8(n: number): void {
this._buff.writeUInt8(n, this._offset, true); this._offset += 1;
this._buff.writeUint8(n, this._offset); this._offset += 1;
}
public readUInt8(): number {
const n = this._buff.readUInt8(this._offset, true); this._offset += 1;
const n = this._buff.readUint8(this._offset); this._offset += 1;
return n;
}
public writeUInt32(n: number): void {
this._buff.writeUInt32BE(n, this._offset, true); this._offset += 4;
this._buff.writeUint32BE(n, this._offset); this._offset += 4;
}
public readUInt32(): number {
const n = this._buff.readUInt32BE(this._offset, true); this._offset += 4;
const n = this._buff.readUint32BE(this._offset); this._offset += 4;
return n;
}
public static sizeShortString(str: string, strByteLength: number): number {
return 1 /* string length */ + strByteLength /* actual string */;
public static sizeShortString(str: VSBuffer): number {
return 1 /* string length */ + str.byteLength /* actual string */;
}
public writeShortString(str: string, strByteLength: number): void {
this._buff.writeUInt8(strByteLength, this._offset, true); this._offset += 1;
this._buff.write(str, this._offset, strByteLength, 'utf8'); this._offset += strByteLength;
public writeShortString(str: VSBuffer): void {
this._buff.writeUint8(str.byteLength, this._offset); this._offset += 1;
this._buff.set(str, this._offset); this._offset += str.byteLength;
}
public readShortString(): string {
const strLength = this._buff.readUInt8(this._offset, true); this._offset += 1;
const str = this._buff.toString('utf8', this._offset, this._offset + strLength); this._offset += strLength;
const strByteLength = this._buff.readUint8(this._offset); this._offset += 1;
const strBuff = this._buff.slice(this._offset, this._offset + strByteLength);
const str = strBuff.toString(); this._offset += strByteLength;
return str;
}
public static sizeLongString(str: string, strByteLength: number): number {
return 4 /* string length */ + strByteLength /* actual string */;
public static sizeLongString(str: VSBuffer): number {
return 4 /* string length */ + str.byteLength /* actual string */;
}
public writeLongString(str: string, strByteLength: number): void {
this._buff.writeUInt32LE(strByteLength, this._offset, true); this._offset += 4;
this._buff.write(str, this._offset, strByteLength, 'utf8'); this._offset += strByteLength;
public writeLongString(str: VSBuffer): void {
this._buff.writeUint32BE(str.byteLength, this._offset); this._offset += 4;
this._buff.set(str, this._offset); this._offset += str.byteLength;
}
public readLongString(): string {
const strLength = this._buff.readUInt32LE(this._offset, true); this._offset += 4;
const str = this._buff.toString('utf8', this._offset, this._offset + strLength); this._offset += strLength;
const strByteLength = this._buff.readUint32BE(this._offset); this._offset += 4;
const strBuff = this._buff.slice(this._offset, this._offset + strByteLength);
const str = strBuff.toString(); this._offset += strByteLength;
return str;
}
public static sizeBuffer(buff: Buffer, buffByteLength: number): number {
return 4 /* buffer length */ + buffByteLength /* actual buffer */;
public static sizeBuffer(buff: VSBuffer): number {
return 4 /* buffer length */ + buff.byteLength /* actual buffer */;
}
public writeBuffer(buff: Buffer, buffByteLength: number): void {
this._buff.writeUInt32LE(buffByteLength, this._offset, true); this._offset += 4;
buff.copy(this._buff, this._offset); this._offset += buffByteLength;
public writeBuffer(buff: VSBuffer): void {
this._buff.writeUint32BE(buff.byteLength, this._offset); this._offset += 4;
this._buff.set(buff, this._offset); this._offset += buff.byteLength;
}
public readBuffer(): Buffer {
const buffLength = this._buff.readUInt32LE(this._offset, true); this._offset += 4;
const buffLength = this._buff.readUint32BE(this._offset); this._offset += 4;
const buff = this._buff.slice(this._offset, this._offset + buffLength); this._offset += buffLength;
return <Buffer>buff.buffer;
}
public static sizeVSBuffer(buff: VSBuffer): number {
return 4 /* buffer length */ + buff.byteLength /* actual buffer */;
}
public writeVSBuffer(buff: VSBuffer): void {
this._buff.writeUint32BE(buff.byteLength, this._offset); this._offset += 4;
this._buff.set(buff, this._offset); this._offset += buff.byteLength;
}
public readVSBuffer(): VSBuffer {
const buffLength = this._buff.readUint32BE(this._offset); this._offset += 4;
const buff = this._buff.slice(this._offset, this._offset + buffLength); this._offset += buffLength;
return buff;
}
public static sizeMixedArray(arr: Array<string | Buffer>, arrLengths: number[]): number {
public static sizeMixedArray(arr: VSBuffer[], arrType: ArgType[]): number {
let size = 0;
size += 1; // arr length
for (let i = 0, len = arr.length; i < len; i++) {
const el = arr[i];
const elLength = arrLengths[i];
const elType = arrType[i];
size += 1; // arg type
if (typeof el === 'string') {
size += this.sizeLongString(el, elLength);
if (elType === ArgType.String) {
size += this.sizeLongString(el);
} else if (elType === ArgType.Buffer) {
size += this.sizeBuffer(el);
} else {
size += this.sizeBuffer(el, elLength);
size += this.sizeVSBuffer(el);
}
}
return size;
}
public writeMixedArray(arr: Array<string | Buffer>, arrLengths: number[]): void {
this._buff.writeUInt8(arr.length, this._offset, true); this._offset += 1;
public writeMixedArray(arr: VSBuffer[], arrType: ArgType[]): void {
this._buff.writeUint8(arr.length, this._offset); this._offset += 1;
for (let i = 0, len = arr.length; i < len; i++) {
const el = arr[i];
const elLength = arrLengths[i];
if (typeof el === 'string') {
const elType = arrType[i];
if (elType === ArgType.String) {
this.writeUInt8(ArgType.String);
this.writeLongString(el, elLength);
} else {
this.writeLongString(el);
} else if (elType === ArgType.Buffer) {
this.writeUInt8(ArgType.Buffer);
this.writeBuffer(el, elLength);
this.writeVSBuffer(el);
} else {
this.writeUInt8(ArgType.VSBuffer);
this.writeVSBuffer(el);
}
}
}
public readMixedArray(): Array<string | Buffer> {
const arrLen = this._buff.readUInt8(this._offset, true); this._offset += 1;
let arr: Array<string | Buffer> = new Array(arrLen);
public readMixedArray(): Array<string | Buffer | VSBuffer> {
const arrLen = this._buff.readUint8(this._offset); this._offset += 1;
let arr: Array<string | Buffer | VSBuffer> = new Array(arrLen);
for (let i = 0; i < arrLen; i++) {
const argType = <ArgType>this.readUInt8();
if (argType === ArgType.String) {
arr[i] = this.readLongString();
} else {
arr[i] = this.readBuffer();
switch (argType) {
case ArgType.String:
arr[i] = this.readLongString();
break;
case ArgType.Buffer:
arr[i] = this.readBuffer();
break;
case ArgType.VSBuffer:
arr[i] = this.readVSBuffer();
break;
}
}
return arr;
@@ -577,42 +611,48 @@ class MessageIO {
if (Buffer.isBuffer(arr[i])) {
return true;
}
if (arr[i] instanceof VSBuffer) {
return true;
}
}
return false;
}
public static serializeRequest(req: number, rpcId: number, method: string, args: any[], usesCancellationToken: boolean, replacer: JSONStringifyReplacer | null): Buffer {
public static serializeRequest(req: number, rpcId: number, method: string, args: any[], usesCancellationToken: boolean, replacer: JSONStringifyReplacer | null): VSBuffer {
if (this._arrayContainsBuffer(args)) {
let massagedArgs: Array<string | Buffer> = new Array(args.length);
let argsLengths: number[] = new Array(args.length);
let massagedArgs: VSBuffer[] = [];
let massagedArgsType: ArgType[] = [];
for (let i = 0, len = args.length; i < len; i++) {
const arg = args[i];
if (Buffer.isBuffer(arg)) {
massagedArgs[i] = VSBuffer.wrap(arg);
massagedArgsType[i] = ArgType.Buffer;
} else if (arg instanceof VSBuffer) {
massagedArgs[i] = arg;
argsLengths[i] = arg.byteLength;
massagedArgsType[i] = ArgType.VSBuffer;
} else {
massagedArgs[i] = safeStringify(arg, replacer);
argsLengths[i] = Buffer.byteLength(massagedArgs[i], 'utf8');
massagedArgs[i] = VSBuffer.fromString(safeStringify(arg, replacer));
massagedArgsType[i] = ArgType.String;
}
}
return this._requestMixedArgs(req, rpcId, method, massagedArgs, argsLengths, usesCancellationToken);
return this._requestMixedArgs(req, rpcId, method, massagedArgs, massagedArgsType, usesCancellationToken);
}
return this._requestJSONArgs(req, rpcId, method, safeStringify(args, replacer), usesCancellationToken);
}
private static _requestJSONArgs(req: number, rpcId: number, method: string, args: string, usesCancellationToken: boolean): Buffer {
const methodByteLength = Buffer.byteLength(method, 'utf8');
const argsByteLength = Buffer.byteLength(args, 'utf8');
private static _requestJSONArgs(req: number, rpcId: number, method: string, args: string, usesCancellationToken: boolean): VSBuffer {
const methodBuff = VSBuffer.fromString(method);
const argsBuff = VSBuffer.fromString(args);
let len = 0;
len += MessageBuffer.sizeUInt8();
len += MessageBuffer.sizeShortString(method, methodByteLength);
len += MessageBuffer.sizeLongString(args, argsByteLength);
len += MessageBuffer.sizeShortString(methodBuff);
len += MessageBuffer.sizeLongString(argsBuff);
let result = MessageBuffer.alloc(usesCancellationToken ? MessageType.RequestJSONArgsWithCancellation : MessageType.RequestJSONArgs, req, len);
result.writeUInt8(rpcId);
result.writeShortString(method, methodByteLength);
result.writeLongString(args, argsByteLength);
result.writeShortString(methodBuff);
result.writeLongString(argsBuff);
return result.buffer;
}
@@ -627,18 +667,18 @@ class MessageIO {
};
}
private static _requestMixedArgs(req: number, rpcId: number, method: string, args: Array<string | Buffer>, argsLengths: number[], usesCancellationToken: boolean): Buffer {
const methodByteLength = Buffer.byteLength(method, 'utf8');
private static _requestMixedArgs(req: number, rpcId: number, method: string, args: VSBuffer[], argsType: ArgType[], usesCancellationToken: boolean): VSBuffer {
const methodBuff = VSBuffer.fromString(method);
let len = 0;
len += MessageBuffer.sizeUInt8();
len += MessageBuffer.sizeShortString(method, methodByteLength);
len += MessageBuffer.sizeMixedArray(args, argsLengths);
len += MessageBuffer.sizeShortString(methodBuff);
len += MessageBuffer.sizeMixedArray(args, argsType);
let result = MessageBuffer.alloc(usesCancellationToken ? MessageType.RequestMixedArgsWithCancellation : MessageType.RequestMixedArgs, req, len);
result.writeUInt8(rpcId);
result.writeShortString(method, methodByteLength);
result.writeMixedArray(args, argsLengths);
result.writeShortString(methodBuff);
result.writeMixedArray(args, argsType);
return result.buffer;
}
@@ -662,36 +702,49 @@ class MessageIO {
};
}
public static serializeAcknowledged(req: number): Buffer {
public static serializeAcknowledged(req: number): VSBuffer {
return MessageBuffer.alloc(MessageType.Acknowledged, req, 0).buffer;
}
public static serializeCancel(req: number): Buffer {
public static serializeCancel(req: number): VSBuffer {
return MessageBuffer.alloc(MessageType.Cancel, req, 0).buffer;
}
public static serializeReplyOK(req: number, res: any, replacer: JSONStringifyReplacer | null): Buffer {
public static serializeReplyOK(req: number, res: any, replacer: JSONStringifyReplacer | null): VSBuffer {
if (typeof res === 'undefined') {
return this._serializeReplyOKEmpty(req);
}
if (Buffer.isBuffer(res)) {
return this._serializeReplyOKBuffer(req, res);
}
if (res instanceof VSBuffer) {
return this._serializeReplyOKVSBuffer(req, res);
}
return this._serializeReplyOKJSON(req, safeStringify(res, replacer));
}
private static _serializeReplyOKEmpty(req: number): Buffer {
private static _serializeReplyOKEmpty(req: number): VSBuffer {
return MessageBuffer.alloc(MessageType.ReplyOKEmpty, req, 0).buffer;
}
private static _serializeReplyOKBuffer(req: number, res: Buffer): Buffer {
const resByteLength = res.byteLength;
private static _serializeReplyOKBuffer(req: number, res: Buffer): VSBuffer {
const buff = VSBuffer.wrap(res);
let len = 0;
len += MessageBuffer.sizeBuffer(res, resByteLength);
len += MessageBuffer.sizeBuffer(buff);
let result = MessageBuffer.alloc(MessageType.ReplyOKBuffer, req, len);
result.writeBuffer(res, resByteLength);
result.writeBuffer(buff);
return result.buffer;
}
private static _serializeReplyOKVSBuffer(req: number, res: VSBuffer): VSBuffer {
let len = 0;
len += MessageBuffer.sizeVSBuffer(res);
let result = MessageBuffer.alloc(MessageType.ReplyOKVSBuffer, req, len);
result.writeVSBuffer(res);
return result.buffer;
}
@@ -699,14 +752,18 @@ class MessageIO {
return buff.readBuffer();
}
private static _serializeReplyOKJSON(req: number, res: string): Buffer {
const resByteLength = Buffer.byteLength(res, 'utf8');
public static deserializeReplyOKVSBuffer(buff: MessageBuffer): VSBuffer {
return buff.readVSBuffer();
}
private static _serializeReplyOKJSON(req: number, res: string): VSBuffer {
const resBuff = VSBuffer.fromString(res);
let len = 0;
len += MessageBuffer.sizeLongString(res, resByteLength);
len += MessageBuffer.sizeLongString(resBuff);
let result = MessageBuffer.alloc(MessageType.ReplyOKJSON, req, len);
result.writeLongString(res, resByteLength);
result.writeLongString(resBuff);
return result.buffer;
}
@@ -715,22 +772,21 @@ class MessageIO {
return JSON.parse(res);
}
public static serializeReplyErr(req: number, err: any): Buffer {
public static serializeReplyErr(req: number, err: any): VSBuffer {
if (err instanceof Error) {
return this._serializeReplyErrEror(req, err);
}
return this._serializeReplyErrEmpty(req);
}
private static _serializeReplyErrEror(req: number, _err: Error): Buffer {
const err = safeStringify(errors.transformErrorForSerialization(_err), null);
const errByteLength = Buffer.byteLength(err, 'utf8');
private static _serializeReplyErrEror(req: number, _err: Error): VSBuffer {
const errBuff = VSBuffer.fromString(safeStringify(errors.transformErrorForSerialization(_err), null));
let len = 0;
len += MessageBuffer.sizeLongString(err, errByteLength);
len += MessageBuffer.sizeLongString(errBuff);
let result = MessageBuffer.alloc(MessageType.ReplyErrError, req, len);
result.writeLongString(err, errByteLength);
result.writeLongString(errBuff);
return result.buffer;
}
@@ -739,7 +795,7 @@ class MessageIO {
return JSON.parse(err);
}
private static _serializeReplyErrEmpty(req: number): Buffer {
private static _serializeReplyErrEmpty(req: number): VSBuffer {
return MessageBuffer.alloc(MessageType.ReplyErrEmpty, req, 0).buffer;
}
}
@@ -753,6 +809,7 @@ const enum MessageType {
Cancel = 6,
ReplyOKEmpty = 7,
ReplyOKBuffer = 8,
ReplyOKVSBuffer = 8,
ReplyOKJSON = 9,
ReplyErrError = 10,
ReplyErrEmpty = 11,
@@ -760,5 +817,6 @@ const enum MessageType {
const enum ArgType {
String = 1,
Buffer = 2
Buffer = 2,
VSBuffer = 3
}

View File

@@ -6,23 +6,24 @@
import * as assert from 'assert';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
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 { VSBuffer } from 'vs/base/common/buffer';
suite('RPCProtocol', () => {
class MessagePassingProtocol implements IMessagePassingProtocol {
private _pair: MessagePassingProtocol;
private readonly _onMessage = new Emitter<Buffer>();
public readonly onMessage: Event<Buffer> = this._onMessage.event;
private readonly _onMessage = new Emitter<VSBuffer>();
public readonly onMessage: Event<VSBuffer> = this._onMessage.event;
public setPair(other: MessagePassingProtocol) {
this._pair = other;
}
public send(buffer: Buffer): void {
public send(buffer: VSBuffer): void {
process.nextTick(() => {
this._pair._onMessage.fire(buffer);
});

View File

@@ -8,7 +8,7 @@ import * as fs from 'fs';
import * as os from 'os';
import * as crypto from 'crypto';
import * as assert from 'assert';
import { isParent, FileOperation, FileOperationEvent, IContent, IResolveFileOptions, IResolveFileResult, IResolveContentOptions, IFileStat, IStreamContent, FileOperationError, FileOperationResult, IUpdateContentOptions, FileChangeType, FileChangesEvent, ICreateFileOptions, IContentData, ITextSnapshot, IFilesConfiguration, IFileSystemProviderRegistrationEvent, IFileSystemProvider, ILegacyFileService, IFileStatWithMetadata, IFileService, IResolveMetadataFileOptions } from 'vs/platform/files/common/files';
import { isParent, FileOperation, FileOperationEvent, IContent, IResolveFileOptions, IResolveFileResult, IResolveContentOptions, IFileStat, IStreamContent, FileOperationError, FileOperationResult, IUpdateContentOptions, FileChangeType, FileChangesEvent, ICreateFileOptions, IContentData, ITextSnapshot, IFilesConfiguration, IFileSystemProviderRegistrationEvent, IFileSystemProvider, ILegacyFileService, IFileStatWithMetadata, IFileService, IResolveMetadataFileOptions, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { MAX_FILE_SIZE, MAX_HEAP_SIZE } from 'vs/platform/files/node/fileConstants';
import { isEqualOrParent } from 'vs/base/common/extpath';
import { ResourceMap } from 'vs/base/common/map';
@@ -207,7 +207,7 @@ export class FileService extends Disposable implements ILegacyFileService, IFile
}
registerProvider(scheme: string, provider: IFileSystemProvider): IDisposable {
throw new Error('not implemented');
return Disposable.None;
}
activateProvider(scheme: string): Promise<void> {
@@ -218,6 +218,10 @@ export class FileService extends Disposable implements ILegacyFileService, IFile
return resource.scheme === Schemas.file;
}
hasCapability(resource: uri, capability: FileSystemProviderCapabilities): Promise<boolean> {
return Promise.resolve(false);
}
resolveContent(resource: uri, options?: IResolveContentOptions): Promise<IContent> {
return this.resolveStreamContent(resource, options).then(streamContent => {
return new Promise<IContent>((resolve, reject) => {

View File

@@ -290,7 +290,7 @@ export class RemoteFileService extends FileService {
options
);
}
if (fileStat.etag === options.etag) {
if (typeof options.etag === 'string' && fileStat.etag === options.etag) {
throw new FileOperationError(
localize('fileNotModifiedError', "File not modified since"),
FileOperationResult.FILE_NOT_MODIFIED_SINCE,
@@ -407,7 +407,7 @@ export class RemoteFileService extends FileService {
return new Promise((resolve, reject) => {
readable.pipe(encoder).pipe(target);
target.once('error', err => reject(err));
target.once('finish', _ => resolve(undefined));
target.once('finish', (_: unknown) => resolve(undefined));
}).then(_ => {
return this.resolveFile(resource, { resolveMetadata: true }) as Promise<IFileStatWithMetadata>;
});
@@ -431,77 +431,6 @@ export class RemoteFileService extends FileService {
});
}
// --- delete
del(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise<void> {
if (resource.scheme === Schemas.file) {
return super.del(resource, options);
} else {
return this._withProvider(resource).then(RemoteFileService._throwIfFileSystemIsReadonly).then(provider => {
return provider.delete(resource, { recursive: !!(options && options.recursive) }).then(() => {
this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.DELETE));
});
});
}
}
copyFile(source: URI, target: URI, overwrite?: boolean): Promise<IFileStatWithMetadata> {
if (source.scheme === target.scheme && source.scheme === Schemas.file) {
return super.copyFile(source, target, overwrite);
}
return this._withProvider(target).then(RemoteFileService._throwIfFileSystemIsReadonly).then(provider => {
if (source.scheme === target.scheme && (provider.capabilities & FileSystemProviderCapabilities.FileFolderCopy)) {
// good: provider supports copy withing scheme
return provider.copy!(source, target, { overwrite: !!overwrite }).then(() => {
return this.resolveFile(target, { resolveMetadata: true });
}).then(fileStat => {
this._onAfterOperation.fire(new FileOperationEvent(source, FileOperation.COPY, fileStat));
return fileStat;
}, err => {
const result = toFileOperationResult(err);
if (result === FileOperationResult.FILE_MOVE_CONFLICT) {
throw new FileOperationError(localize('fileMoveConflict', "Unable to move/copy. File already exists at destination."), result);
}
throw err;
});
}
const prepare = overwrite
? Promise.resolve(this.del(target, { recursive: true }).catch(_err => { /*ignore*/ }))
: Promise.resolve();
// todo@ben, can only copy text files
// https://github.com/Microsoft/vscode/issues/41543
return prepare.then(() => {
return this.resolveContent(source, { acceptTextOnly: true }).then(content => {
return this._withProvider(target).then(provider => {
return this._writeFile(
provider, target,
new StringSnapshot(content.value),
content.encoding,
{ create: true, overwrite: !!overwrite }
).then(fileStat => {
this._onAfterOperation.fire(new FileOperationEvent(source, FileOperation.COPY, fileStat));
return fileStat;
});
}, err => {
const result = toFileOperationResult(err);
if (result === FileOperationResult.FILE_MOVE_CONFLICT) {
throw new FileOperationError(localize('fileMoveConflict', "Unable to move/copy. File already exists at destination."), result);
} else if (err instanceof Error && err.name === 'ENOPRO') {
// file scheme
return super.updateContent(target, content.value, { encoding: content.encoding });
} else {
return Promise.reject(err);
}
});
});
});
});
}
private _activeWatches = new Map<string, { unwatch: Promise<IDisposable>, count: number }>();
watchFileChanges(resource: URI, opts: IWatchOptions = { recursive: false, excludes: [] }): void {

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getNextTickChannel } from 'vs/base/parts/ipc/node/ipc';
import { getNextTickChannel } from 'vs/base/parts/ipc/common/ipc';
import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
import { toFileChangesEvent, IRawFileChange } from 'vs/workbench/services/files/node/watcher/common';
import { WatcherChannelClient } from 'vs/workbench/services/files/node/watcher/nsfw/watcherIpc';

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getNextTickChannel } from 'vs/base/parts/ipc/node/ipc';
import { getNextTickChannel } from 'vs/base/parts/ipc/common/ipc';
import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
import { toFileChangesEvent, IRawFileChange } from 'vs/workbench/services/files/node/watcher/common';
import { WatcherChannelClient } from 'vs/workbench/services/files/node/watcher/unix/watcherIpc';

View File

@@ -4,11 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable, IDisposable, toDisposable, combinedDisposable } 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 } from 'vs/platform/files/common/files';
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 } 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';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { isAbsolutePath, dirname, basename, joinPath, isEqual, isEqualOrParent } from 'vs/base/common/resources';
import { localize } from 'vs/nls';
import { TernarySearchTree } from 'vs/base/common/map';
@@ -20,21 +19,34 @@ export class FileService2 extends Disposable implements IFileService {
//#region TODO@Ben HACKS
private _impl: IFileService;
private _legacy: IFileService | null;
setImpl(service: IFileService): void {
this._impl = this._register(service);
setLegacyService(legacy: IFileService): void {
this._legacy = this._register(legacy);
this._register(service.onFileChanges(e => this._onFileChanges.fire(e)));
this._register(service.onAfterOperation(e => this._onAfterOperation.fire(e)));
this._register(legacy.onFileChanges(e => this._onFileChanges.fire(e)));
this._register(legacy.onAfterOperation(e => this._onAfterOperation.fire(e)));
this.provider.forEach((provider, scheme) => {
legacy.registerProvider(scheme, provider);
});
this.joinOnImplResolve(legacy);
}
//#endregion
_serviceBrand: ServiceIdentifier<any>;
private joinOnLegacy: Promise<IFileService>;
private joinOnImplResolve: (service: IFileService) => void;
constructor(@ILogService private logService: ILogService) {
super();
this.joinOnLegacy = new Promise(resolve => {
this.joinOnImplResolve = resolve;
});
}
//#region File System Provider
@@ -53,8 +65,8 @@ export class FileService2 extends Disposable implements IFileService {
}
let legacyDisposal: IDisposable;
if (this._impl) {
legacyDisposal = this._impl.registerProvider(scheme, provider);
if (this._legacy) {
legacyDisposal = this._legacy.registerProvider(scheme, provider);
} else {
legacyDisposal = Disposable.None;
}
@@ -104,6 +116,12 @@ export class FileService2 extends Disposable implements IFileService {
return this.provider.has(resource.scheme);
}
async hasCapability(resource: URI, capability: FileSystemProviderCapabilities): Promise<boolean> {
const provider = await this.withProvider(resource);
return !!(provider.capabilities & capability);
}
private async withProvider(resource: URI): Promise<IFileSystemProvider> {
// Assert path is absolute
@@ -119,9 +137,9 @@ export class FileService2 extends Disposable implements IFileService {
if (!provider) {
const err = new Error();
err.name = 'ENOPRO';
err.message = `no provider for ${resource.toString()}`;
err.message = `No provider found for ${resource.toString()}`;
return Promise.reject(err);
throw err;
}
return provider;
@@ -211,7 +229,7 @@ export class FileService2 extends Disposable implements IFileService {
const childResource = joinPath(resource, name);
const childStat = resolveMetadata ? await provider.stat(childResource) : { type };
return this.toFileStat(provider, childResource, childStat, entries.length, resolveMetadata, recurse);
return await this.toFileStat(provider, childResource, childStat, entries.length, resolveMetadata, recurse);
} catch (error) {
this.logService.trace(error);
@@ -236,43 +254,20 @@ export class FileService2 extends Disposable implements IFileService {
async resolveFiles(toResolve: { resource: URI, options?: IResolveFileOptions }[]): Promise<IResolveFileResult[]>;
async resolveFiles(toResolve: { resource: URI, options: IResolveMetadataFileOptions }[]): Promise<IResolveFileResult[]>;
async resolveFiles(toResolve: { resource: URI; options?: IResolveFileOptions; }[]): Promise<IResolveFileResult[]> {
return Promise.all(toResolve.map(async entry => {
try {
return { stat: await this.doResolveFile(entry.resource, entry.options), success: true };
} catch (error) {
this.logService.trace(error);
// soft-groupBy, keep order, don't rearrange/merge groups
const groups: Array<typeof toResolve> = [];
let group: typeof toResolve | undefined;
for (const request of toResolve) {
if (!group || group[0].resource.scheme !== request.resource.scheme) {
group = [];
groups.push(group);
return { stat: undefined, success: false };
}
group.push(request);
}
// resolve files (in parallel)
const result: Promise<IResolveFileResult>[] = [];
for (const group of groups) {
for (const groupEntry of group) {
result.push((async () => {
try {
return { stat: await this.doResolveFile(groupEntry.resource, groupEntry.options), success: true };
} catch (error) {
this.logService.trace(error);
return { stat: undefined, success: false };
}
})());
}
}
return Promise.all(result);
}));
}
async existsFile(resource: URI): Promise<boolean> {
try {
await this.resolveFile(resource);
return true;
return !!(await this.resolveFile(resource));
} catch (error) {
return false;
}
@@ -282,43 +277,85 @@ export class FileService2 extends Disposable implements IFileService {
//#region File Reading/Writing
get encoding(): IResourceEncodings { return this._impl.encoding; }
get encoding(): IResourceEncodings {
if (!this._legacy) {
throw new Error('Legacy file service not ready yet');
}
createFile(resource: URI, content?: string, options?: ICreateFileOptions): Promise<IFileStatWithMetadata> {
return this._impl.createFile(resource, content, options);
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));
// validate overwrite
const overwrite = !!(options && options.overwrite);
if (await this.existsFile(resource)) {
if (!overwrite) {
throw new FileOperationError(localize('fileExists', "File to create already exists ({0})", resource.toString(true)), FileOperationResult.FILE_MODIFIED_SINCE, options);
}
// delete otherwise
await this.del(resource, { recursive: true });
}
try {
// mkdir recursively
await this.mkdirp(provider, dirname(resource));
// create file: buffered
if (hasOpenReadWriteCloseCapability(provider)) {
await this.doWriteBuffered(provider, resource, new TextEncoder().encode(content));
}
// create file: unbuffered
else if (hasReadWriteCapability(provider)) {
await this.doWriteUnbuffered(provider, resource, new TextEncoder().encode(content), overwrite);
}
// give up if provider has insufficient capabilities
else {
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);
}
// events
const fileStat = await this.resolveFile(resource, { resolveMetadata: true });
this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.CREATE, fileStat));
return fileStat;
}
resolveContent(resource: URI, options?: IResolveContentOptions): Promise<IContent> {
return this._impl.resolveContent(resource, options);
return this.joinOnLegacy.then(legacy => legacy.resolveContent(resource, options));
}
resolveStreamContent(resource: URI, options?: IResolveContentOptions): Promise<IStreamContent> {
return this._impl.resolveStreamContent(resource, options);
return this.joinOnLegacy.then(legacy => legacy.resolveStreamContent(resource, options));
}
updateContent(resource: URI, value: string | ITextSnapshot, options?: IUpdateContentOptions): Promise<IFileStatWithMetadata> {
return this._impl.updateContent(resource, value, options);
return this.joinOnLegacy.then(legacy => legacy.updateContent(resource, value, options));
}
//#endregion
//#region Move/Copy/Delete/Create Folder
moveFile(source: URI, target: URI, overwrite?: boolean): Promise<IFileStatWithMetadata> {
if (source.scheme === target.scheme) {
return this.doMoveCopyWithSameProvider(source, target, false /* just move */, overwrite);
}
async moveFile(source: URI, target: URI, overwrite?: boolean): Promise<IFileStatWithMetadata> {
const sourceProvider = this.throwIfFileSystemIsReadonly(await this.withProvider(source));
const targetProvider = this.throwIfFileSystemIsReadonly(await this.withProvider(target));
return this.doMoveWithDifferentProvider(source, target);
}
private async doMoveWithDifferentProvider(source: URI, target: URI, overwrite?: boolean): Promise<IFileStatWithMetadata> {
// copy file source => target
await this.copyFile(source, target, overwrite);
// delete source
await this.del(source, { recursive: true });
// move
await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'move', overwrite);
// resolve and send events
const fileStat = await this.resolveFile(target, { resolveMetadata: true });
@@ -328,68 +365,146 @@ export class FileService2 extends Disposable implements IFileService {
}
async copyFile(source: URI, target: URI, overwrite?: boolean): Promise<IFileStatWithMetadata> {
if (source.scheme === target.scheme) {
return this.doCopyWithSameProvider(source, target, overwrite);
}
const sourceProvider = await this.withProvider(source);
const targetProvider = this.throwIfFileSystemIsReadonly(await this.withProvider(target));
return this.doCopyWithDifferentProvider(source, target);
// copy
await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'copy', overwrite);
// resolve and send events
const fileStat = await this.resolveFile(target, { resolveMetadata: true });
this._onAfterOperation.fire(new FileOperationEvent(source, FileOperation.COPY, fileStat));
return fileStat;
}
private async doCopyWithSameProvider(source: URI, target: URI, overwrite: boolean = false): Promise<IFileStatWithMetadata> {
const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(source));
// check if provider supports fast file/folder copy
if (provider.capabilities & FileSystemProviderCapabilities.FileFolderCopy && typeof provider.copy === 'function') {
return this.doMoveCopyWithSameProvider(source, target, true /* keep copy */, overwrite);
}
return this._impl.copyFile(source, target, overwrite); // TODO@ben implement properly
}
private async doCopyWithDifferentProvider(source: URI, target: URI, overwrite?: boolean): Promise<IFileStatWithMetadata> {
return this._impl.copyFile(source, target, overwrite); // TODO@ben implement properly
}
private async doMoveCopyWithSameProvider(source: URI, target: URI, keepCopy: boolean, overwrite?: boolean): Promise<IFileStatWithMetadata> {
const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(source));
private async doMoveCopy(sourceProvider: IFileSystemProvider, source: URI, targetProvider: IFileSystemProvider, target: URI, mode: 'move' | 'copy', overwrite?: boolean): Promise<void> {
// validation
const isPathCaseSensitive = !!(provider.capabilities & FileSystemProviderCapabilities.PathCaseSensitive);
const 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")));
const { exists, isCaseChange } = await this.doValidateMoveCopy(sourceProvider, source, targetProvider, target, overwrite);
// delete as needed
if (exists && !isCaseChange && overwrite) {
await this.del(target, { recursive: true });
}
// create parent folders
await this.mkdirp(targetProvider, dirname(target));
// copy source => target
if (mode === 'copy') {
// same provider with fast copy: leverage copy() functionality
if (sourceProvider === targetProvider && hasFileFolderCopyCapability(sourceProvider)) {
return sourceProvider.copy(source, target, { overwrite: !!overwrite });
}
// otherwise, ensure we got the capabilities to do this
if (
!(hasOpenReadWriteCloseCapability(sourceProvider) || hasReadWriteCapability(sourceProvider)) ||
!(hasOpenReadWriteCloseCapability(targetProvider) || hasReadWriteCapability(targetProvider))
) {
return Promise.reject('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.resolveFile(source);
if (sourceFile.isDirectory) {
return this.doCopyFolder(sourceProvider, sourceFile, targetProvider, target, overwrite);
} else {
return this.doCopyFile(sourceProvider, source, targetProvider, target, overwrite);
}
}
// move source => target
else {
// same provider: leverage rename() functionality
if (sourceProvider === targetProvider) {
return sourceProvider.rename(source, target, { overwrite: !!overwrite });
}
// across providers: copy to target & delete at source
else {
await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'copy', overwrite);
return this.del(source, { recursive: true });
}
}
}
private async doCopyFile(sourceProvider: IFileSystemProvider, source: URI, targetProvider: IFileSystemProvider, target: URI, overwrite?: boolean): Promise<void> {
// copy: source (buffered) => target (buffered)
if (hasOpenReadWriteCloseCapability(sourceProvider) && hasOpenReadWriteCloseCapability(targetProvider)) {
return this.doPipeBuffered(sourceProvider, source, targetProvider, target);
}
// copy: source (buffered) => target (unbuffered)
if (hasOpenReadWriteCloseCapability(sourceProvider) && hasReadWriteCapability(targetProvider)) {
return this.doPipeBufferedToUnbuffered(sourceProvider, source, targetProvider, target, !!overwrite);
}
// copy: source (unbuffered) => target (buffered)
if (hasReadWriteCapability(sourceProvider) && hasOpenReadWriteCloseCapability(targetProvider)) {
return this.doPipeUnbufferedToBuffered(sourceProvider, source, targetProvider, target);
}
// copy: source (unbuffered) => target (unbuffered)
if (hasReadWriteCapability(sourceProvider) && hasReadWriteCapability(targetProvider)) {
return this.doPipeUnbuffered(sourceProvider, source, targetProvider, target, !!overwrite);
}
}
private async doCopyFolder(sourceProvider: IFileSystemProvider, sourceFolder: IFileStat, targetProvider: IFileSystemProvider, targetFolder: URI, overwrite?: boolean): Promise<void> {
// create folder in target
await targetProvider.mkdir(targetFolder);
// create children in target
if (Array.isArray(sourceFolder.children)) {
await Promise.all(sourceFolder.children.map(async sourceChild => {
const targetChild = joinPath(targetFolder, sourceChild.name);
if (sourceChild.isDirectory) {
return this.doCopyFolder(sourceProvider, await this.resolveFile(sourceChild.resource), targetProvider, targetChild, overwrite);
} else {
return this.doCopyFile(sourceProvider, sourceChild.resource, targetProvider, targetChild, overwrite);
}
}));
}
}
private async doValidateMoveCopy(sourceProvider: IFileSystemProvider, source: URI, targetProvider: IFileSystemProvider, target: URI, overwrite?: boolean): Promise<{ exists: boolean, isCaseChange: boolean }> {
let isCaseChange = false;
let isPathCaseSensitive = false;
// Check if source is equal or parent to target (requires providers to be the same)
if (sourceProvider === targetProvider) {
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")));
}
}
// Extra checks if target exists and this is not a rename
const exists = await this.existsFile(target);
if (exists && !isCaseChange) {
// Bail out if target exists and we are not about to overwrite
if (!overwrite) {
throw new FileOperationError(localize('unableToMoveCopyError2', "Unable to move/copy. File already exists at destination."), FileOperationResult.FILE_MOVE_CONFLICT);
}
// 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 (isEqualOrParent(source, target, !isPathCaseSensitive)) {
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.")));
}
await this.del(target, { recursive: true });
}
// create parent folders
await this.mkdirp(provider, dirname(target));
// rename/copy source => target
if (keepCopy) {
await provider.copy!(source, target, { overwrite: !!overwrite });
} else {
await provider.rename(source, target, { overwrite: !!overwrite });
}
// resolve and send events
const fileStat = await this.resolveFile(target, { resolveMetadata: true });
this._onAfterOperation.fire(new FileOperationEvent(source, keepCopy ? FileOperation.COPY : FileOperation.MOVE, fileStat));
return fileStat;
return { exists, isCaseChange };
}
async createFolder(resource: URI): Promise<IFileStatWithMetadata> {
@@ -440,14 +555,25 @@ export class FileService2 extends Disposable implements IFileService {
}
async del(resource: URI, options?: { useTrash?: boolean; recursive?: boolean; }): Promise<void> {
if (options && options.useTrash) {
return this._impl.del(resource, options); //TODO@ben this is https://github.com/Microsoft/vscode/issues/48259
}
const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(resource));
// Validate trash support
const useTrash = !!(options && options.useTrash);
if (useTrash && !(provider.capabilities & FileSystemProviderCapabilities.Trash)) {
throw new Error(localize('err.trash', "Provider does not support trash."));
}
// Validate recursive
const recursive = !!(options && options.recursive);
if (!recursive && await this.existsFile(resource)) {
const stat = await this.resolveFile(resource);
if (stat.isDirectory && Array.isArray(stat.children) && stat.children.length > 0) {
throw new Error(localize('deleteFailed', "Failed to delete non-empty folder '{0}'.", resource.toString()));
}
}
// Delete through provider
await provider.delete(resource, { recursive: !!(options && options.recursive) });
await provider.delete(resource, { recursive, useTrash });
// Events
this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.DELETE));
@@ -461,17 +587,121 @@ export class FileService2 extends Disposable implements IFileService {
get onFileChanges(): Event<FileChangesEvent> { return this._onFileChanges.event; }
watchFileChanges(resource: URI): void {
this._impl.watchFileChanges(resource);
this.joinOnLegacy.then(legacy => legacy.watchFileChanges(resource));
}
unwatchFileChanges(resource: URI): void {
this._impl.unwatchFileChanges(resource);
this.joinOnLegacy.then(legacy => legacy.unwatchFileChanges(resource));
}
//#endregion
//#region Helpers
private async doWriteBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, buffer: Uint8Array): Promise<void> {
// Open handle
const handle = await provider.open(resource, { create: true });
// write into handle until all bytes from buffer have been written
await this.doWriteBuffer(provider, handle, buffer, buffer.byteLength, 0, 0);
// Close handle
return provider.close(handle);
}
private async doWriteBuffer(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, handle: number, buffer: Uint8Array, length: number, posInFile: number, posInBuffer: number): Promise<void> {
let totalBytesWritten = 0;
while (totalBytesWritten < length) {
const bytesWritten = await provider.write(handle, posInFile + totalBytesWritten, buffer, posInBuffer + totalBytesWritten, length - totalBytesWritten);
totalBytesWritten += bytesWritten;
}
}
private async doWriteUnbuffered(provider: IFileSystemProviderWithFileReadWriteCapability, resource: URI, buffer: Uint8Array, overwrite: boolean): Promise<void> {
return provider.writeFile(resource, buffer, { create: true, overwrite });
}
private async doPipeBuffered(sourceProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, source: URI, targetProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI): Promise<void> {
// Open handles
const sourceHandle = await sourceProvider.open(source, { create: false });
const targetHandle = await targetProvider.open(target, { create: true });
const buffer = new Uint8Array(8 * 1024);
let posInFile = 0;
let posInBuffer = 0;
let bytesRead = 0;
do {
// read from source (sourceHandle) at current position (posInFile) into buffer (buffer) at
// buffer position (posInBuffer) up to the size of the buffer (buffer.byteLength).
bytesRead = await sourceProvider.read(sourceHandle, posInFile, buffer, posInBuffer, buffer.byteLength - posInBuffer);
// write into target (targetHandle) at current position (posInFile) from buffer (buffer) at
// buffer position (posInBuffer) all bytes we read (bytesRead).
await this.doWriteBuffer(targetProvider, targetHandle, buffer, bytesRead, posInFile, posInBuffer);
posInFile += bytesRead;
posInBuffer += bytesRead;
// when buffer full, fill it again from the beginning
if (posInBuffer === buffer.length) {
posInBuffer = 0;
}
} while (bytesRead > 0);
// Close handles
return Promise.all([
sourceProvider.close(sourceHandle),
targetProvider.close(targetHandle)
]).then(() => undefined);
}
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 doPipeUnbufferedToBuffered(sourceProvider: IFileSystemProviderWithFileReadWriteCapability, source: URI, targetProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI): Promise<void> {
// Open handle
const targetHandle = await targetProvider.open(target, { create: true });
// Read entire buffer from source and write buffered
const buffer = await sourceProvider.readFile(source);
await this.doWriteBuffer(targetProvider, targetHandle, buffer, buffer.byteLength, 0, 0);
// Close handle
return targetProvider.close(targetHandle);
}
private async doPipeBufferedToUnbuffered(sourceProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, source: URI, targetProvider: IFileSystemProviderWithFileReadWriteCapability, target: URI, overwrite: boolean): Promise<void> {
// Determine file size
const size = (await this.resolveFile(source, { resolveMetadata: true })).size;
// Open handle
const sourceHandle = await sourceProvider.open(source, { create: false });
const buffer = new Uint8Array(size);
let pos = 0;
let bytesRead = 0;
do {
// read from source (sourceHandle) at current position (posInFile) into buffer (buffer) at
// buffer position (posInBuffer) up to the size of the buffer (buffer.byteLength).
bytesRead = await sourceProvider.read(sourceHandle, pos, buffer, pos, buffer.byteLength - pos);
pos += bytesRead;
} while (bytesRead > 0);
// Write buffer into target at once
await this.doWriteUnbuffered(targetProvider, target, buffer, overwrite);
// Close handle
return sourceProvider.close(sourceHandle);
}
private throwIfFileSystemIsReadonly(provider: IFileSystemProvider): IFileSystemProvider {
if (provider.capabilities & FileSystemProviderCapabilities.Readonly) {
throw new FileOperationError(localize('err.readonly', "Resource can not be modified."), FileOperationResult.FILE_PERMISSION_DENIED);
@@ -482,5 +712,3 @@ export class FileService2 extends Disposable implements IFileService {
//#endregion
}
registerSingleton(IFileService, FileService2);

View File

@@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { shell } from 'electron';
import { DiskFileSystemProvider as NodeDiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider';
import { FileDeleteOptions, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { isWindows } from 'vs/base/common/platform';
import { localize } from 'vs/nls';
import { basename } from 'vs/base/common/path';
export class DiskFileSystemProvider extends NodeDiskFileSystemProvider {
get capabilities(): FileSystemProviderCapabilities {
if (!this._capabilities) {
this._capabilities = super.capabilities | FileSystemProviderCapabilities.Trash;
}
return this._capabilities;
}
protected async doDelete(filePath: string, opts: FileDeleteOptions): Promise<void> {
if (!opts.useTrash) {
return super.doDelete(filePath, opts);
}
const result = shell.moveItemToTrash(filePath);
if (!result) {
throw new Error(isWindows ? localize('binFailed', "Failed to move '{0}' to the recycle bin", basename(filePath)) : localize('trashFailed', "Failed to move '{0}' to the trash", basename(filePath)));
}
}
}

View File

@@ -3,17 +3,19 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { mkdir } from 'fs';
import { mkdir, open, close, read, write } from 'fs';
import { tmpdir } from 'os';
import { promisify } from 'util';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, FileSystemProviderErrorCode, createFileSystemProviderError, FileSystemProviderError } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { isLinux } from 'vs/base/common/platform';
import { statLink, readdir, unlink, del, move, copy } from 'vs/base/node/pfs';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { statLink, readdir, unlink, del, move, copy, readFile, writeFile, fileExists, truncate } from 'vs/base/node/pfs';
import { normalize } from 'vs/base/common/path';
import { joinPath } from 'vs/base/common/resources';
import { isEqual } from 'vs/base/common/extpath';
import { retry } from 'vs/base/common/async';
export class DiskFileSystemProvider extends Disposable implements IFileSystemProvider {
@@ -21,7 +23,7 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
onDidChangeCapabilities: Event<void> = Event.None;
private _capabilities: FileSystemProviderCapabilities;
protected _capabilities: FileSystemProviderCapabilities;
get capabilities(): FileSystemProviderCapabilities {
if (!this._capabilities) {
this._capabilities =
@@ -45,8 +47,15 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
try {
const { stat, isSymbolicLink } = await statLink(this.toFilePath(resource)); // cannot use fs.stat() here to support links properly
let type: number;
if (isSymbolicLink) {
type = FileType.SymbolicLink | (stat.isDirectory() ? FileType.Directory : FileType.File);
} else {
type = stat.isFile() ? FileType.File : stat.isDirectory() ? FileType.Directory : FileType.Unknown;
}
return {
type: isSymbolicLink ? FileType.SymbolicLink : stat.isFile() ? FileType.File : stat.isDirectory() ? FileType.Directory : FileType.Unknown,
type,
ctime: stat.ctime.getTime(),
mtime: stat.mtime.getTime(),
size: stat.size
@@ -78,28 +87,112 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
//#region File Reading/Writing
readFile(resource: URI): Promise<Uint8Array> {
throw new Error('Method not implemented.');
async readFile(resource: URI): Promise<Uint8Array> {
try {
const filePath = this.toFilePath(resource);
return await readFile(filePath);
} catch (error) {
throw this.toFileSystemProviderError(error);
}
}
writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> {
throw new Error('Method not implemented.');
async writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> {
try {
const filePath = this.toFilePath(resource);
// Validate target
const exists = await fileExists(filePath);
if (exists && !opts.overwrite) {
throw createFileSystemProviderError(new Error('File already exists'), FileSystemProviderErrorCode.FileExists);
} else if (!exists && !opts.create) {
throw createFileSystemProviderError(new Error('File does not exist'), FileSystemProviderErrorCode.FileNotFound);
}
if (exists && isWindows) {
try {
// On Windows and if the file exists, we use a different strategy of saving the file
// by first truncating the file and then writing with r+ mode. This helps to save hidden files on Windows
// (see https://github.com/Microsoft/vscode/issues/931) and prevent removing alternate data streams
// (see https://github.com/Microsoft/vscode/issues/6363)
await truncate(filePath, 0);
// We heard from one user that fs.truncate() succeeds, but the save fails (https://github.com/Microsoft/vscode/issues/61310)
// In that case, the file is now entirely empty and the contents are gone. This can happen if an external file watcher is
// installed that reacts on the truncate and keeps the file busy right after. Our workaround is to retry to save after a
// short timeout, assuming that the file is free to write then.
await retry(() => writeFile(filePath, content, { flag: 'r+' }), 100 /* ms delay */, 3 /* retries */);
} catch (error) {
// we heard from users that fs.truncate() fails (https://github.com/Microsoft/vscode/issues/59561)
// in that case we simply save the file without truncating first (same as macOS and Linux)
await writeFile(filePath, content);
}
}
// macOS/Linux: just write directly
else {
await writeFile(filePath, content);
}
} catch (error) {
throw this.toFileSystemProviderError(error);
}
}
open(resource: URI, opts: FileOpenOptions): Promise<number> {
throw new Error('Method not implemented.');
async open(resource: URI, opts: FileOpenOptions): Promise<number> {
try {
const filePath = this.toFilePath(resource);
let mode: string;
if (opts.create) {
// we take this as a hint that the file is opened for writing
// as such we use 'w' to truncate an existing or create the
// file otherwise. we do not allow reading.
mode = 'w';
} else {
// otherwise we assume the file is opened for reading
// as such we use 'r' to neither truncate, nor create
// the file.
mode = 'r';
}
return await promisify(open)(filePath, mode);
} catch (error) {
throw this.toFileSystemProviderError(error);
}
}
close(fd: number): Promise<void> {
throw new Error('Method not implemented.');
async close(fd: number): Promise<void> {
try {
return await promisify(close)(fd);
} catch (error) {
throw this.toFileSystemProviderError(error);
}
}
read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
throw new Error('Method not implemented.');
async read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
try {
const result = await promisify(read)(fd, data, offset, length, pos);
if (typeof result === 'number') {
return result; // node.d.ts fail
}
return result.bytesRead;
} catch (error) {
throw this.toFileSystemProviderError(error);
}
}
write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
throw new Error('Method not implemented.');
async write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
try {
const result = await promisify(write)(fd, data, offset, length, pos);
if (typeof result === 'number') {
return result; // node.d.ts fail
}
return result.bytesWritten;
} catch (error) {
throw this.toFileSystemProviderError(error);
}
}
//#endregion
@@ -118,11 +211,7 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
try {
const filePath = this.toFilePath(resource);
if (opts.recursive) {
await del(filePath, tmpdir());
} else {
await unlink(filePath);
}
await this.doDelete(filePath, opts);
} catch (error) {
if (error.code === 'ENOENT') {
return Promise.resolve(); // tolerate that the file might not exist
@@ -132,11 +221,23 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
}
}
protected async doDelete(filePath: string, opts: FileDeleteOptions): Promise<void> {
if (opts.recursive) {
await del(filePath, tmpdir());
} else {
await unlink(filePath);
}
}
async rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> {
try {
const fromFilePath = this.toFilePath(from);
const toFilePath = this.toFilePath(to);
// Ensure target does not exist
await this.validateTargetDeleted(from, to, opts && opts.overwrite);
// Move
await move(fromFilePath, toFilePath);
} catch (error) {
throw this.toFileSystemProviderError(error);
@@ -148,12 +249,33 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
const fromFilePath = this.toFilePath(from);
const toFilePath = this.toFilePath(to);
return copy(fromFilePath, toFilePath);
// Ensure target does not exist
await this.validateTargetDeleted(from, to, opts && opts.overwrite);
// Copy
await copy(fromFilePath, toFilePath);
} catch (error) {
throw this.toFileSystemProviderError(error);
}
}
private async validateTargetDeleted(from: URI, to: URI, overwrite?: boolean): Promise<void> {
const fromFilePath = this.toFilePath(from);
const toFilePath = this.toFilePath(to);
const isPathCaseSensitive = !!(this.capabilities & FileSystemProviderCapabilities.PathCaseSensitive);
const isCaseChange = isPathCaseSensitive ? false : isEqual(fromFilePath, toFilePath, true /* ignore case */);
// handle existing target (unless this is a case change)
if (!isCaseChange && await fileExists(toFilePath)) {
if (!overwrite) {
throw createFileSystemProviderError(new Error('File at target already exists'), FileSystemProviderErrorCode.FileExists);
}
await this.delete(to, { recursive: true, useTrash: false });
}
}
//#endregion
//#region File Watching
@@ -169,7 +291,7 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
//#region Helpers
private toFilePath(resource: URI): string {
protected toFilePath(resource: URI): string {
return normalize(resource.fsPath);
}

View File

@@ -1,19 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IFileService } from 'vs/platform/files/common/files';
import { Schemas } from 'vs/base/common/network';
import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider';
import { Disposable } from 'vs/base/common/lifecycle';
export class DiskFileSystemSupport extends Disposable implements IWorkbenchContribution {
constructor(@IFileService fileService: IFileService) {
super();
this._register(fileService.registerProvider(Schemas.file, new DiskFileSystemProvider()));
}
}

View File

@@ -6,7 +6,7 @@
import * as assert from 'assert';
import { FileService2 } from 'vs/workbench/services/files2/common/fileService2';
import { URI } from 'vs/base/common/uri';
import { IFileSystemProviderRegistrationEvent } from 'vs/platform/files/common/files';
import { IFileSystemProviderRegistrationEvent, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { IDisposable } from 'vs/base/common/lifecycle';
import { NullFileSystemProvider } from 'vs/workbench/test/workbenchTestServices';
import { NullLogService } from 'vs/platform/log/common/log';
@@ -15,8 +15,9 @@ suite('File Service 2', () => {
test('provider registration', async () => {
const service = new FileService2(new NullLogService());
const resource = URI.parse('test://foo/bar');
assert.equal(service.canHandleResource(URI.parse('test://foo/bar')), false);
assert.equal(service.canHandleResource(resource), false);
const registrations: IFileSystemProviderRegistrationEvent[] = [];
service.onDidChangeFileSystemProviderRegistrations(e => {
@@ -39,7 +40,7 @@ suite('File Service 2', () => {
await service.activateProvider('test');
assert.equal(service.canHandleResource(URI.parse('test://foo/bar')), true);
assert.equal(service.canHandleResource(resource), true);
assert.equal(registrations.length, 1);
assert.equal(registrations[0].scheme, 'test');
@@ -49,9 +50,12 @@ suite('File Service 2', () => {
await service.activateProvider('test');
assert.equal(callCount, 2); // activation is called again
assert.equal(await service.hasCapability(resource, FileSystemProviderCapabilities.Readonly), true);
assert.equal(await service.hasCapability(resource, FileSystemProviderCapabilities.FileOpenReadWriteClose), false);
registrationDisposable!.dispose();
assert.equal(service.canHandleResource(URI.parse('test://foo/bar')), false);
assert.equal(service.canHandleResource(resource), false);
assert.equal(registrations.length, 2);
assert.equal(registrations[1].scheme, 'test');

View File

@@ -14,14 +14,11 @@ import { join, basename, dirname, posix } from 'vs/base/common/path';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { copy, del } from 'vs/base/node/pfs';
import { URI } from 'vs/base/common/uri';
import { existsSync, statSync, readdirSync } from 'fs';
import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult } from 'vs/platform/files/common/files';
import { FileService } from 'vs/workbench/services/files/node/fileService';
import { TestContextService, TestEnvironmentService, TestTextResourceConfigurationService, TestLifecycleService, TestStorageService } from 'vs/workbench/test/workbenchTestServices';
import { Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
import { existsSync, statSync, readdirSync, readFileSync } from 'fs';
import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { NullLogService } from 'vs/platform/log/common/log';
import { isLinux } from 'vs/base/common/platform';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
function getByName(root: IFileStat, name: string): IFileStat | null {
if (root.children === undefined) {
@@ -37,80 +34,104 @@ function getByName(root: IFileStat, name: string): IFileStat | null {
return null;
}
export class TestDiskFileSystemProvider extends DiskFileSystemProvider {
private _testCapabilities: FileSystemProviderCapabilities;
get capabilities(): FileSystemProviderCapabilities {
if (!this._testCapabilities) {
this._testCapabilities =
FileSystemProviderCapabilities.FileReadWrite |
FileSystemProviderCapabilities.FileOpenReadWriteClose |
FileSystemProviderCapabilities.FileFolderCopy;
if (isLinux) {
this._testCapabilities |= FileSystemProviderCapabilities.PathCaseSensitive;
}
}
return this._testCapabilities;
}
set capabilities(capabilities: FileSystemProviderCapabilities) {
this._testCapabilities = capabilities;
}
}
suite('Disk File Service', () => {
const parentDir = getRandomTestPath(tmpdir(), 'vsctests', 'diskfileservice');
const testSchema = 'test';
let service: FileService2;
let fileProvider: TestDiskFileSystemProvider;
let testProvider: TestDiskFileSystemProvider;
let testDir: string;
let disposables: IDisposable[] = [];
setup(async () => {
service = new FileService2(new NullLogService());
service.registerProvider(Schemas.file, new DiskFileSystemProvider());
disposables.push(service);
fileProvider = new TestDiskFileSystemProvider();
service.registerProvider(Schemas.file, fileProvider);
testProvider = new TestDiskFileSystemProvider();
service.registerProvider(testSchema, testProvider);
const id = generateUuid();
testDir = join(parentDir, id);
const sourceDir = getPathFromAmdModule(require, './fixtures/service');
await copy(sourceDir, testDir);
const legacyService = new FileService(new TestContextService(new Workspace(testDir, toWorkspaceFolders([{ path: testDir }]))), TestEnvironmentService, new TestTextResourceConfigurationService(), new TestConfigurationService(), new TestLifecycleService(), new TestStorageService(), new TestNotificationService(), { disableWatcher: true });
service.setImpl(legacyService);
});
teardown(async () => {
service.dispose();
disposables = dispose(disposables);
await del(parentDir, tmpdir());
});
test('createFolder', async () => {
let event: FileOperationEvent | undefined;
const toDispose = service.onAfterOperation(e => {
event = e;
});
disposables.push(service.onAfterOperation(e => event = e));
const parent = await service.resolveFile(URI.file(testDir));
const resource = URI.file(join(parent.resource.fsPath, 'newFolder'));
const newFolderResource = URI.file(join(parent.resource.fsPath, 'newFolder'));
const folder = await service.createFolder(resource);
const newFolder = await service.createFolder(newFolderResource);
assert.equal(folder.name, 'newFolder');
assert.equal(existsSync(folder.resource.fsPath), true);
assert.equal(newFolder.name, 'newFolder');
assert.equal(existsSync(newFolder.resource.fsPath), true);
assert.ok(event);
assert.equal(event!.resource.fsPath, resource.fsPath);
assert.equal(event!.resource.fsPath, newFolderResource.fsPath);
assert.equal(event!.operation, FileOperation.CREATE);
assert.equal(event!.target!.resource.fsPath, resource.fsPath);
assert.equal(event!.target!.resource.fsPath, newFolderResource.fsPath);
assert.equal(event!.target!.isDirectory, true);
toDispose.dispose();
});
test('createFolder: creating multiple folders at once', async function () {
let event: FileOperationEvent;
const toDispose = service.onAfterOperation(e => {
event = e;
});
disposables.push(service.onAfterOperation(e => event = e));
const multiFolderPaths = ['a', 'couple', 'of', 'folders'];
const parent = await service.resolveFile(URI.file(testDir));
const resource = URI.file(join(parent.resource.fsPath, ...multiFolderPaths));
const newFolderResource = URI.file(join(parent.resource.fsPath, ...multiFolderPaths));
const folder = await service.createFolder(resource);
const newFolder = await service.createFolder(newFolderResource);
const lastFolderName = multiFolderPaths[multiFolderPaths.length - 1];
assert.equal(folder.name, lastFolderName);
assert.equal(existsSync(folder.resource.fsPath), true);
assert.equal(newFolder.name, lastFolderName);
assert.equal(existsSync(newFolder.resource.fsPath), true);
assert.ok(event!);
assert.equal(event!.resource.fsPath, resource.fsPath);
assert.equal(event!.resource.fsPath, newFolderResource.fsPath);
assert.equal(event!.operation, FileOperation.CREATE);
assert.equal(event!.target!.resource.fsPath, resource.fsPath);
assert.equal(event!.target!.resource.fsPath, newFolderResource.fsPath);
assert.equal(event!.target!.isDirectory, true);
toDispose.dispose();
});
test('existsFile', async () => {
@@ -288,9 +309,7 @@ suite('Disk File Service', () => {
test('deleteFile', async () => {
let event: FileOperationEvent;
const toDispose = service.onAfterOperation(e => {
event = e;
});
disposables.push(service.onAfterOperation(e => event = e));
const resource = URI.file(join(testDir, 'deep', 'conway.js'));
const source = await service.resolveFile(resource);
@@ -301,15 +320,11 @@ suite('Disk File Service', () => {
assert.ok(event!);
assert.equal(event!.resource.fsPath, resource.fsPath);
assert.equal(event!.operation, FileOperation.DELETE);
toDispose.dispose();
});
test('deleteFolder (recursive)', async () => {
let event: FileOperationEvent;
const toDispose = service.onAfterOperation(e => {
event = e;
});
disposables.push(service.onAfterOperation(e => event = e));
const resource = URI.file(join(testDir, 'deep'));
const source = await service.resolveFile(resource);
@@ -320,8 +335,6 @@ suite('Disk File Service', () => {
assert.ok(event!);
assert.equal(event!.resource.fsPath, resource.fsPath);
assert.equal(event!.operation, FileOperation.DELETE);
toDispose.dispose();
});
test('deleteFolder (non recursive)', async () => {
@@ -329,128 +342,7 @@ suite('Disk File Service', () => {
const source = await service.resolveFile(resource);
try {
await service.del(source.resource);
return Promise.reject(new Error('Unexpected'));
}
catch (error) {
return Promise.resolve(true);
}
});
test('renameFile', async () => {
let event: FileOperationEvent;
const toDispose = service.onAfterOperation(e => {
event = e;
});
const resource = URI.file(join(testDir, 'index.html'));
const source = await service.resolveFile(resource);
const renamed = await service.moveFile(source.resource, URI.file(join(dirname(source.resource.fsPath), 'other.html')));
assert.equal(existsSync(renamed.resource.fsPath), true);
assert.equal(existsSync(source.resource.fsPath), false);
assert.ok(event!);
assert.equal(event!.resource.fsPath, resource.fsPath);
assert.equal(event!.operation, FileOperation.MOVE);
assert.equal(event!.target!.resource.fsPath, renamed.resource.fsPath);
toDispose.dispose();
});
test('renameFile - multi folder', async () => {
let event: FileOperationEvent;
const toDispose = service.onAfterOperation(e => {
event = e;
});
const multiFolderPaths = ['a', 'couple', 'of', 'folders'];
const renameToPath = join(...multiFolderPaths, 'other.html');
const resource = URI.file(join(testDir, 'index.html'));
const source = await service.resolveFile(resource);
const renamed = await service.moveFile(source.resource, URI.file(join(dirname(source.resource.fsPath), renameToPath)));
assert.equal(existsSync(renamed.resource.fsPath), true);
assert.equal(existsSync(source.resource.fsPath), false);
assert.ok(event!);
assert.equal(event!.resource.fsPath, resource.fsPath);
assert.equal(event!.operation, FileOperation.MOVE);
assert.equal(event!.target!.resource.fsPath, renamed.resource.fsPath);
toDispose.dispose();
});
test('renameFolder', async () => {
let event: FileOperationEvent;
const toDispose = service.onAfterOperation(e => {
event = e;
});
const resource = URI.file(join(testDir, 'deep'));
const source = await service.resolveFile(resource);
const renamed = await service.moveFile(source.resource, URI.file(join(dirname(source.resource.fsPath), 'deeper')));
assert.equal(existsSync(renamed.resource.fsPath), true);
assert.equal(existsSync(source.resource.fsPath), false);
assert.ok(event!);
assert.equal(event!.resource.fsPath, resource.fsPath);
assert.equal(event!.operation, FileOperation.MOVE);
assert.equal(event!.target!.resource.fsPath, renamed.resource.fsPath);
toDispose.dispose();
});
test('renameFolder - multi folder', async () => {
let event: FileOperationEvent;
const toDispose = service.onAfterOperation(e => {
event = e;
});
const multiFolderPaths = ['a', 'couple', 'of', 'folders'];
const renameToPath = join(...multiFolderPaths);
const resource = URI.file(join(testDir, 'deep'));
const source = await service.resolveFile(resource);
const renamed = await service.moveFile(source.resource, URI.file(join(dirname(source.resource.fsPath), renameToPath)));
assert.equal(existsSync(renamed.resource.fsPath), true);
assert.equal(existsSync(source.resource.fsPath), false);
assert.ok(event!);
assert.equal(event!.resource.fsPath, resource.fsPath);
assert.equal(event!.operation, FileOperation.MOVE);
assert.equal(event!.target!.resource.fsPath, renamed.resource.fsPath);
toDispose.dispose();
});
test('renameFile - MIX CASE', function () {
let event: FileOperationEvent;
const toDispose = service.onAfterOperation(e => {
event = e;
});
const resource = URI.file(join(testDir, 'index.html'));
return service.resolveFile(resource).then(source => {
return service.moveFile(source.resource, URI.file(join(dirname(source.resource.fsPath), 'INDEX.html'))).then(renamed => {
assert.equal(existsSync(renamed.resource.fsPath), true);
assert.equal(basename(renamed.resource.fsPath), 'INDEX.html');
assert.ok(event);
assert.equal(event.resource.fsPath, resource.fsPath);
assert.equal(event.operation, FileOperation.MOVE);
assert.equal(event.target!.resource.fsPath, renamed.resource.fsPath);
toDispose.dispose();
});
});
});
test('deleteFolder (non recursive)', async () => {
const resource = URI.file(join(testDir, 'deep'));
const source = await service.resolveFile(resource);
try {
await service.del(source.resource);
return Promise.reject(new Error('Unexpected'));
}
catch (error) {
@@ -460,30 +352,216 @@ suite('Disk File Service', () => {
test('moveFile', async () => {
let event: FileOperationEvent;
const toDispose = service.onAfterOperation(e => {
event = e;
});
disposables.push(service.onAfterOperation(e => event = e));
const resource = URI.file(join(testDir, 'index.html'));
const source = await service.resolveFile(resource);
const source = URI.file(join(testDir, 'index.html'));
const sourceContents = readFileSync(source.fsPath);
const renamed = await service.moveFile(source.resource, URI.file(join(testDir, 'other.html')));
const target = URI.file(join(dirname(source.fsPath), 'other.html'));
const renamed = await service.moveFile(source, target);
assert.equal(existsSync(renamed.resource.fsPath), true);
assert.equal(existsSync(source.resource.fsPath), false);
assert.equal(existsSync(source.fsPath), false);
assert.ok(event!);
assert.equal(event!.resource.fsPath, resource.fsPath);
assert.equal(event!.resource.fsPath, source.fsPath);
assert.equal(event!.operation, FileOperation.MOVE);
assert.equal(event!.target!.resource.fsPath, renamed.resource.fsPath);
toDispose.dispose();
const targetContents = readFileSync(target.fsPath);
assert.equal(sourceContents.byteLength, targetContents.byteLength);
assert.equal(sourceContents.toString(), targetContents.toString());
});
test('move - source parent of target', async () => {
test('moveFile - across providers (buffered => buffered)', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose);
setCapabilities(testProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose);
await testMoveAcrossProviders();
});
test('moveFile - across providers (unbuffered => unbuffered)', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite);
setCapabilities(testProvider, FileSystemProviderCapabilities.FileReadWrite);
await testMoveAcrossProviders();
});
test('moveFile - across providers (buffered => unbuffered)', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose);
setCapabilities(testProvider, FileSystemProviderCapabilities.FileReadWrite);
await testMoveAcrossProviders();
});
test('moveFile - across providers (unbuffered => buffered)', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite);
setCapabilities(testProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose);
await testMoveAcrossProviders();
});
test('moveFile - across providers - large (buffered => buffered)', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose);
setCapabilities(testProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose);
await testMoveAcrossProviders('lorem.txt');
});
test('moveFile - across providers - large (unbuffered => unbuffered)', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite);
setCapabilities(testProvider, FileSystemProviderCapabilities.FileReadWrite);
await testMoveAcrossProviders('lorem.txt');
});
test('moveFile - across providers - large (buffered => unbuffered)', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose);
setCapabilities(testProvider, FileSystemProviderCapabilities.FileReadWrite);
await testMoveAcrossProviders('lorem.txt');
});
test('moveFile - across providers - large (unbuffered => buffered)', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite);
setCapabilities(testProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose);
await testMoveAcrossProviders('lorem.txt');
});
async function testMoveAcrossProviders(sourceFile = 'index.html'): Promise<void> {
let event: FileOperationEvent;
const toDispose = service.onAfterOperation(e => {
event = e;
});
disposables.push(service.onAfterOperation(e => event = e));
const source = URI.file(join(testDir, sourceFile));
const sourceContents = readFileSync(source.fsPath);
const target = URI.file(join(dirname(source.fsPath), 'other.html')).with({ scheme: testSchema });
const renamed = await service.moveFile(source, target);
assert.equal(existsSync(renamed.resource.fsPath), true);
assert.equal(existsSync(source.fsPath), false);
assert.ok(event!);
assert.equal(event!.resource.fsPath, source.fsPath);
assert.equal(event!.operation, FileOperation.MOVE);
assert.equal(event!.target!.resource.fsPath, renamed.resource.fsPath);
const targetContents = readFileSync(target.fsPath);
assert.equal(sourceContents.byteLength, targetContents.byteLength);
assert.equal(sourceContents.toString(), targetContents.toString());
}
test('moveFile - multi folder', async () => {
let event: FileOperationEvent;
disposables.push(service.onAfterOperation(e => event = e));
const multiFolderPaths = ['a', 'couple', 'of', 'folders'];
const renameToPath = join(...multiFolderPaths, 'other.html');
const source = URI.file(join(testDir, 'index.html'));
const renamed = await service.moveFile(source, URI.file(join(dirname(source.fsPath), renameToPath)));
assert.equal(existsSync(renamed.resource.fsPath), true);
assert.equal(existsSync(source.fsPath), false);
assert.ok(event!);
assert.equal(event!.resource.fsPath, source.fsPath);
assert.equal(event!.operation, FileOperation.MOVE);
assert.equal(event!.target!.resource.fsPath, renamed.resource.fsPath);
});
test('moveFile - directory', async () => {
let event: FileOperationEvent;
disposables.push(service.onAfterOperation(e => event = e));
const source = URI.file(join(testDir, 'deep'));
const renamed = await service.moveFile(source, URI.file(join(dirname(source.fsPath), 'deeper')));
assert.equal(existsSync(renamed.resource.fsPath), true);
assert.equal(existsSync(source.fsPath), false);
assert.ok(event!);
assert.equal(event!.resource.fsPath, source.fsPath);
assert.equal(event!.operation, FileOperation.MOVE);
assert.equal(event!.target!.resource.fsPath, renamed.resource.fsPath);
});
test('moveFile - directory - across providers (buffered => buffered)', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose);
setCapabilities(testProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose);
await testMoveFolderAcrossProviders();
});
test('moveFile - directory - across providers (unbuffered => unbuffered)', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite);
setCapabilities(testProvider, FileSystemProviderCapabilities.FileReadWrite);
await testMoveFolderAcrossProviders();
});
test('moveFile - directory - across providers (buffered => unbuffered)', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose);
setCapabilities(testProvider, FileSystemProviderCapabilities.FileReadWrite);
await testMoveFolderAcrossProviders();
});
test('moveFile - directory - across providers (unbuffered => buffered)', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite);
setCapabilities(testProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose);
await testMoveFolderAcrossProviders();
});
async function testMoveFolderAcrossProviders(): Promise<void> {
let event: FileOperationEvent;
disposables.push(service.onAfterOperation(e => event = e));
const source = URI.file(join(testDir, 'deep'));
const sourceChildren = readdirSync(source.fsPath);
const target = URI.file(join(dirname(source.fsPath), 'deeper')).with({ scheme: testSchema });
const renamed = await service.moveFile(source, target);
assert.equal(existsSync(renamed.resource.fsPath), true);
assert.equal(existsSync(source.fsPath), false);
assert.ok(event!);
assert.equal(event!.resource.fsPath, source.fsPath);
assert.equal(event!.operation, FileOperation.MOVE);
assert.equal(event!.target!.resource.fsPath, renamed.resource.fsPath);
const targetChildren = readdirSync(target.fsPath);
assert.equal(sourceChildren.length, targetChildren.length);
for (let i = 0; i < sourceChildren.length; i++) {
assert.equal(sourceChildren[i], targetChildren[i]);
}
}
test('moveFile - MIX CASE', async () => {
let event: FileOperationEvent;
disposables.push(service.onAfterOperation(e => event = e));
const source = URI.file(join(testDir, 'index.html'));
await service.resolveFile(source);
const renamed = await service.moveFile(source, URI.file(join(dirname(source.fsPath), 'INDEX.html')));
assert.equal(existsSync(renamed.resource.fsPath), true);
assert.equal(basename(renamed.resource.fsPath), 'INDEX.html');
assert.ok(event!);
assert.equal(event!.resource.fsPath, source.fsPath);
assert.equal(event!.operation, FileOperation.MOVE);
assert.equal(event!.target!.resource.fsPath, renamed.resource.fsPath);
});
test('moveFile - source parent of target', async () => {
let event: FileOperationEvent;
disposables.push(service.onAfterOperation(e => event = e));
await service.resolveFile(URI.file(join(testDir, 'index.html')));
try {
@@ -491,15 +569,12 @@ suite('Disk File Service', () => {
} catch (e) {
assert.ok(e);
assert.ok(!event!);
toDispose.dispose();
}
});
test('move - FILE_MOVE_CONFLICT', async () => {
test('moveFile - FILE_MOVE_CONFLICT', async () => {
let event: FileOperationEvent;
const toDispose = service.onAfterOperation(e => {
event = e;
});
disposables.push(service.onAfterOperation(e => event = e));
const source = await service.resolveFile(URI.file(join(testDir, 'index.html')));
try {
@@ -507,36 +582,14 @@ suite('Disk File Service', () => {
} catch (e) {
assert.equal(e.fileOperationResult, FileOperationResult.FILE_MOVE_CONFLICT);
assert.ok(!event!);
toDispose.dispose();
}
});
test('moveFile - MIX CASE', async () => {
let event: FileOperationEvent;
const toDispose = service.onAfterOperation(e => {
event = e;
});
const resource = URI.file(join(testDir, 'index.html'));
const source = await service.resolveFile(resource);
const renamed = await service.moveFile(source.resource, URI.file(join(testDir, 'INDEX.html')));
assert.equal(existsSync(renamed.resource.fsPath), true);
assert.equal(basename(renamed.resource.fsPath), 'INDEX.html');
assert.ok(event!);
assert.equal(event!.resource.fsPath, resource.fsPath);
assert.equal(event!.operation, FileOperation.MOVE);
assert.equal(event!.target!.resource.fsPath, renamed.resource.fsPath);
toDispose.dispose();
});
test('moveFile - overwrite folder with file', async () => {
let createEvent: FileOperationEvent;
let moveEvent: FileOperationEvent;
let deleteEvent: FileOperationEvent;
const toDispose = service.onAfterOperation(e => {
disposables.push(service.onAfterOperation(e => {
if (e.operation === FileOperation.CREATE) {
createEvent = e;
} else if (e.operation === FileOperation.DELETE) {
@@ -544,37 +597,68 @@ suite('Disk File Service', () => {
} else if (e.operation === FileOperation.MOVE) {
moveEvent = e;
}
});
}));
const parent = await service.resolveFile(URI.file(testDir));
const folderResource = URI.file(join(parent.resource.fsPath, 'conway.js'));
const f = await service.createFolder(folderResource);
const resource = URI.file(join(testDir, 'deep', 'conway.js'));
const source = URI.file(join(testDir, 'deep', 'conway.js'));
const moved = await service.moveFile(resource, f.resource, true);
const moved = await service.moveFile(source, f.resource, true);
assert.equal(existsSync(moved.resource.fsPath), true);
assert.ok(statSync(moved.resource.fsPath).isFile);
assert.ok(createEvent!);
assert.ok(deleteEvent!);
assert.ok(moveEvent!);
assert.equal(moveEvent!.resource.fsPath, resource.fsPath);
assert.equal(moveEvent!.resource.fsPath, source.fsPath);
assert.equal(moveEvent!.target!.resource.fsPath, moved.resource.fsPath);
assert.equal(deleteEvent!.resource.fsPath, folderResource.fsPath);
toDispose.dispose();
});
test('copyFile', async () => {
await doTestCopyFile();
});
test('copyFile - unbuffered (FileSystemProviderCapabilities.FileReadWrite)', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite);
await doTestCopyFile();
});
test('copyFile - unbuffered large (FileSystemProviderCapabilities.FileReadWrite)', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite);
await doTestCopyFile('lorem.txt');
});
test('copyFile - buffered (FileSystemProviderCapabilities.FileOpenReadWriteClose)', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose);
await doTestCopyFile();
});
test('copyFile - buffered large (FileSystemProviderCapabilities.FileOpenReadWriteClose)', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose);
await doTestCopyFile('lorem.txt');
});
function setCapabilities(provider: TestDiskFileSystemProvider, capabilities: FileSystemProviderCapabilities): void {
provider.capabilities = capabilities;
if (isLinux) {
provider.capabilities |= FileSystemProviderCapabilities.PathCaseSensitive;
}
}
async function doTestCopyFile(sourceName: string = 'index.html') {
let event: FileOperationEvent;
const toDispose = service.onAfterOperation(e => {
event = e;
});
disposables.push(service.onAfterOperation(e => event = e));
const source = await service.resolveFile(URI.file(join(testDir, 'index.html')));
const resource = URI.file(join(testDir, 'other.html'));
const source = await service.resolveFile(URI.file(join(testDir, sourceName)));
const target = URI.file(join(testDir, 'other.html'));
const copied = await service.copyFile(source.resource, resource);
const copied = await service.copyFile(source.resource, target);
assert.equal(existsSync(copied.resource.fsPath), true);
assert.equal(existsSync(source.resource.fsPath), true);
@@ -582,14 +666,19 @@ suite('Disk File Service', () => {
assert.equal(event!.resource.fsPath, source.resource.fsPath);
assert.equal(event!.operation, FileOperation.COPY);
assert.equal(event!.target!.resource.fsPath, copied.resource.fsPath);
toDispose.dispose();
});
const sourceContents = readFileSync(source.resource.fsPath);
const targetContents = readFileSync(target.fsPath);
assert.equal(sourceContents.byteLength, targetContents.byteLength);
assert.equal(sourceContents.toString(), targetContents.toString());
}
test('copyFile - overwrite folder with file', async () => {
let createEvent: FileOperationEvent;
let copyEvent: FileOperationEvent;
let deleteEvent: FileOperationEvent;
const toDispose = service.onAfterOperation(e => {
disposables.push(service.onAfterOperation(e => {
if (e.operation === FileOperation.CREATE) {
createEvent = e;
} else if (e.operation === FileOperation.DELETE) {
@@ -597,25 +686,23 @@ suite('Disk File Service', () => {
} else if (e.operation === FileOperation.COPY) {
copyEvent = e;
}
});
}));
const parent = await service.resolveFile(URI.file(testDir));
const folderResource = URI.file(join(parent.resource.fsPath, 'conway.js'));
const f = await service.createFolder(folderResource);
const resource = URI.file(join(testDir, 'deep', 'conway.js'));
const source = URI.file(join(testDir, 'deep', 'conway.js'));
const copied = await service.copyFile(resource, f.resource, true);
const copied = await service.copyFile(source, f.resource, true);
assert.equal(existsSync(copied.resource.fsPath), true);
assert.ok(statSync(copied.resource.fsPath).isFile);
assert.ok(createEvent!);
assert.ok(deleteEvent!);
assert.ok(copyEvent!);
assert.equal(copyEvent!.resource.fsPath, resource.fsPath);
assert.equal(copyEvent!.resource.fsPath, source.fsPath);
assert.equal(copyEvent!.target!.resource.fsPath, copied.resource.fsPath);
assert.equal(deleteEvent!.resource.fsPath, folderResource.fsPath);
toDispose.dispose();
});
test('copyFile - MIX CASE', async () => {
@@ -623,6 +710,7 @@ suite('Disk File Service', () => {
const renamed = await service.moveFile(source.resource, URI.file(join(dirname(source.resource.fsPath), 'CONWAY.js')));
assert.equal(existsSync(renamed.resource.fsPath), true);
assert.ok(readdirSync(testDir).some(f => f === 'CONWAY.js'));
const source_1 = await service.resolveFile(URI.file(join(testDir, 'deep', 'conway.js')));
const targetParent = URI.file(testDir);
const target = targetParent.with({ path: posix.join(targetParent.path, posix.basename(source_1.resource.path)) });

View File

@@ -36,6 +36,7 @@ import { DEFAULT_SETTINGS_EDITOR_SETTING, FOLDER_SETTINGS_PATH, getSettingsTarge
import { DefaultPreferencesEditorInput, KeybindingsEditorInput, PreferencesEditorInput, SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput';
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';
const emptyEditableSettingsContent = '{\n}';
@@ -69,7 +70,8 @@ export class PreferencesService extends Disposable implements IPreferencesServic
@IModelService private readonly modelService: IModelService,
@IJSONEditingService private readonly jsonEditingService: IJSONEditingService,
@IModeService private readonly modeService: IModeService,
@ILabelService private readonly labelService: ILabelService
@ILabelService private readonly labelService: ILabelService,
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService
) {
super();
// The default keybindings.json updates based on keyboard layouts, so here we make sure
@@ -210,6 +212,15 @@ export class PreferencesService extends Disposable implements IPreferencesServic
this.openOrSwitchSettings2(ConfigurationTarget.USER, undefined, options, group);
}
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 } });
}
return null;
}
openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise<IEditor | null> {
jsonEditor = typeof jsonEditor === 'undefined' ?
this.configurationService.getValue('workbench.settings.editor') === 'json' :

View File

@@ -201,6 +201,7 @@ export interface IPreferencesService {
openRawDefaultSettings(): Promise<IEditor | null>;
openSettings(jsonEditor?: boolean): Promise<IEditor | null>;
openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise<IEditor | null>;
openRemoteSettings(): Promise<IEditor | null>;
openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise<IEditor | null>;
openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise<IEditor | null>;
switchSettings(target: ConfigurationTarget, resource: URI, jsonEditor?: boolean): Promise<void>;

View File

@@ -0,0 +1,113 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { Disposable } from 'vs/base/common/lifecycle';
import { IChannel, IServerChannel, getDelayedChannel } from 'vs/base/parts/ipc/common/ipc';
import { Client } from 'vs/base/parts/ipc/common/ipc.net';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { connectRemoteAgentManagement, IConnectionOptions, IWebSocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
import { IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { RemoteAgentConnectionContext, IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions';
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';
export abstract class AbstractRemoteAgentService extends Disposable implements IRemoteAgentService {
_serviceBrand: any;
private _environment: Promise<IRemoteAgentEnvironment | null> | null;
constructor(
@IEnvironmentService protected readonly _environmentService: IEnvironmentService
) {
super();
}
abstract getConnection(): IRemoteAgentConnection | null;
getEnvironment(bail?: boolean): Promise<IRemoteAgentEnvironment | null> {
if (!this._environment) {
const connection = this.getConnection();
if (connection) {
const client = new RemoteExtensionEnvironmentChannelClient(connection.getChannel('remoteextensionsenvironment'));
this._environment = client.getEnvironmentData(connection.remoteAuthority, this._environmentService.extensionDevelopmentLocationURI);
} else {
this._environment = Promise.resolve(null);
}
}
return bail ? this._environment : this._environment.then(undefined, () => null);
}
}
export class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection {
readonly remoteAuthority: string;
private _connection: Promise<Client<RemoteAgentConnectionContext>> | null;
constructor(
remoteAuthority: string,
private _commit: string | undefined,
private _webSocketFactory: IWebSocketFactory,
private _environmentService: IEnvironmentService,
private _remoteAuthorityResolverService: IRemoteAuthorityResolverService
) {
super();
this.remoteAuthority = remoteAuthority;
this._connection = null;
}
getChannel<T extends IChannel>(channelName: string): T {
return <T>getDelayedChannel(this._getOrCreateConnection().then(c => c.getChannel(channelName)));
}
registerChannel<T extends IServerChannel<RemoteAgentConnectionContext>>(channelName: string, channel: T): void {
this._getOrCreateConnection().then(client => client.registerChannel(channelName, channel));
}
private _getOrCreateConnection(): Promise<Client<RemoteAgentConnectionContext>> {
if (!this._connection) {
this._connection = this._createConnection();
}
return this._connection;
}
private async _createConnection(): Promise<Client<RemoteAgentConnectionContext>> {
const options: IConnectionOptions = {
isBuilt: this._environmentService.isBuilt,
commit: this._commit,
webSocketFactory: this._webSocketFactory,
addressProvider: {
getAddress: async () => {
const { host, port } = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority);
return { host, port };
}
}
};
const connection = await connectRemoteAgentManagement(options, this.remoteAuthority, `renderer`);
this._register(connection);
return connection.client;
}
}
class RemoteConnectionFailureNotificationContribution implements IWorkbenchContribution {
constructor(
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@INotificationService notificationService: INotificationService,
) {
// Let's cover the case where connecting to fetch the remote extension info fails
remoteAgentService.getEnvironment(true)
.then(undefined, err => notificationService.error(nls.localize('connectionError', "Failed to connect to the remote extension host agent (Error: {0})", err ? err.message : '')));
}
}
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(Extensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(RemoteConnectionFailureNotificationContribution, LifecyclePhase.Ready);

View File

@@ -15,7 +15,7 @@ export interface IRemoteAgentService {
_serviceBrand: any;
getConnection(): IRemoteAgentConnection | null;
getEnvironment(): Promise<IRemoteAgentEnvironment | null>;
getEnvironment(bail?: boolean): Promise<IRemoteAgentEnvironment | null>;
}
export interface IRemoteAgentConnection {

View File

@@ -3,109 +3,29 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { getDelayedChannel } from 'vs/base/parts/ipc/node/ipc';
import { Client } from 'vs/base/parts/ipc/node/ipc.net';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { connectRemoteAgentManagement } from 'vs/platform/remote/node/remoteAgentConnection';
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAgentConnection } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { RemoteAgentConnectionContext, IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions';
import { Registry } from 'vs/platform/registry/common/platform';
import { RemoteExtensionEnvironmentChannelClient } from 'vs/workbench/services/remote/node/remoteAgentEnvironmentChannel';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { localize } from 'vs/nls';
import product from 'vs/platform/product/node/product';
import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory';
import { AbstractRemoteAgentService, RemoteAgentConnection } from 'vs/workbench/services/remote/common/abstractRemoteAgentService';
export class RemoteAgentService extends Disposable implements IRemoteAgentService {
_serviceBrand: any;
export class RemoteAgentService extends AbstractRemoteAgentService {
private readonly _connection: IRemoteAgentConnection | null = null;
private _environment: Promise<IRemoteAgentEnvironment | null> | null;
constructor(
{ remoteAuthority }: IWindowConfiguration,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
constructor({ remoteAuthority }: IWindowConfiguration,
@IEnvironmentService environmentService: IEnvironmentService,
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService
) {
super();
super(environmentService);
if (remoteAuthority) {
this._connection = this._register(new RemoteAgentConnection(remoteAuthority, _environmentService, remoteAuthorityResolverService));
this._connection = this._register(new RemoteAgentConnection(remoteAuthority, product.commit, nodeWebSocketFactory, environmentService, remoteAuthorityResolverService));
}
}
getConnection(): IRemoteAgentConnection | null {
return this._connection;
}
getEnvironment(bail?: boolean): Promise<IRemoteAgentEnvironment | null> {
if (!this._environment) {
const connection = this.getConnection();
if (connection) {
const client = new RemoteExtensionEnvironmentChannelClient(connection.getChannel('remoteextensionsenvironment'));
this._environment = client.getEnvironmentData(connection.remoteAuthority, this._environmentService.extensionDevelopmentLocationURI);
} else {
this._environment = Promise.resolve(null);
}
}
return bail ? this._environment : this._environment.then(undefined, () => null);
}
}
class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection {
readonly remoteAuthority: string;
private _connection: Promise<Client<RemoteAgentConnectionContext>> | null;
constructor(
remoteAuthority: string,
private _environmentService: IEnvironmentService,
private _remoteAuthorityResolverService: IRemoteAuthorityResolverService
) {
super();
this.remoteAuthority = remoteAuthority;
this._connection = null;
}
getChannel<T extends IChannel>(channelName: string): T {
return <T>getDelayedChannel(this._getOrCreateConnection().then(c => c.getChannel(channelName)));
}
registerChannel<T extends IServerChannel<RemoteAgentConnectionContext>>(channelName: string, channel: T): void {
this._getOrCreateConnection().then(client => client.registerChannel(channelName, channel));
}
private _getOrCreateConnection(): Promise<Client<RemoteAgentConnectionContext>> {
if (!this._connection) {
this._connection = this._createConnection();
}
return this._connection;
}
private async _createConnection(): Promise<Client<RemoteAgentConnectionContext>> {
const resolvedAuthority = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority);
const connection = await connectRemoteAgentManagement(this.remoteAuthority, resolvedAuthority.host, resolvedAuthority.port, `renderer`, this._environmentService.isBuilt);
this._register(connection);
return connection.client;
}
}
class RemoteConnectionFailureNotificationContribution implements IWorkbenchContribution {
constructor(
@IRemoteAgentService remoteAgentService: RemoteAgentService,
@INotificationService notificationService: INotificationService,
) {
// Let's cover the case where connecting to fetch the remote extension info fails
remoteAgentService.getEnvironment(true)
.then(undefined, err => notificationService.error(localize('connectionError', "Failed to connect to the remote extension host agent (Error: {0})", err ? err.message : '')));
}
}
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(Extensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(RemoteConnectionFailureNotificationContribution, LifecyclePhase.Ready);

View File

@@ -136,10 +136,10 @@ class FileSearchEngine {
if (results) {
results.forEach(result => {
const relativePath = path.relative(fq.folder.fsPath, result.fsPath);
const relativePath = path.posix.relative(fq.folder.path, result.path);
if (noSiblingsClauses) {
const basename = path.basename(result.fsPath);
const basename = path.basename(result.path);
this.matchFile(onResult, { base: fq.folder, relativePath, basename });
return;

View File

@@ -140,6 +140,10 @@ export function rgErrorMsgForDisplay(msg: string): Maybe<SearchError> {
return new SearchError(firstLine.charAt(0).toUpperCase() + firstLine.substr(1), SearchErrorCode.invalidLiteral);
}
if (startsWith(firstLine, 'PCRE2: error compiling pattern')) {
return new SearchError(firstLine, SearchErrorCode.regexParseError);
}
return undefined;
}

View File

@@ -14,7 +14,7 @@ import { Schemas } from 'vs/base/common/network';
import { StopWatch } from 'vs/base/common/stopwatch';
import { URI as uri } from 'vs/base/common/uri';
import * as pfs from 'vs/base/node/pfs';
import { getNextTickChannel } from 'vs/base/parts/ipc/node/ipc';
import { getNextTickChannel } from 'vs/base/parts/ipc/common/ipc';
import { Client, IIPCOptions } from 'vs/base/parts/ipc/node/ipc.cp';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -403,13 +403,6 @@ export class SearchService extends Disposable implements ISearchService {
}
private matches(resource: uri, query: ITextQuery): boolean {
// includes
if (query.includePattern) {
if (resource.scheme !== Schemas.file) {
return false; // if we match on file patterns, we have to ignore non file resources
}
}
return pathIncludedInQuery(query, resource.fsPath);
}

View File

@@ -153,7 +153,7 @@ export class FileIconThemeStore {
});
}
public findThemeDataByParentLocation(parentLocation: URI | undefined): any {
public findThemeDataByParentLocation(parentLocation: URI | undefined): Promise<FileIconThemeData[]> {
if (parentLocation) {
return this.getFileIconThemes().then(allThemes => {
return allThemes.filter(t => t.location && resources.isEqualOrParent(t.location, parentLocation));

View File

@@ -56,7 +56,7 @@ export interface IWorkbenchThemeService extends IThemeService {
getColorTheme(): IColorTheme;
getColorThemes(): Promise<IColorTheme[]>;
onDidColorThemeChange: Event<IColorTheme>;
restoreColorTheme();
restoreColorTheme(): void;
setFileIconTheme(iconThemeId: string | undefined, settingsTarget: ConfigurationTarget | undefined): Promise<IFileIconTheme>;
getFileIconTheme(): IFileIconTheme;