mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Merge from vscode 966b87dd4013be1a9c06e2b8334522ec61905cc2 (#4696)
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {};
|
||||
}
|
||||
|
||||
@@ -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']);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
|
||||
@@ -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)) });
|
||||
|
||||
@@ -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' :
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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);
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user