From a5ea66dee55de7e0b29259bdb5460fe43b4c181c Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 23 Sep 2019 10:29:15 +0200 Subject: [PATCH] Refactor file dialog service to fix layer breaker (#81171) Fixes #81161 --- .../electron-browser/remote.contribution.ts | 2 +- .../browser/abstractFileDialogService.ts | 194 ++++++++++++ .../dialogs/browser/fileDialogService.ts | 292 +----------------- ...emoteFileDialog.ts => simpleFileDialog.ts} | 6 +- .../electron-browser/fileDialogService.ts | 177 +++++++++++ src/vs/workbench/workbench.common.main.ts | 1 - src/vs/workbench/workbench.desktop.main.ts | 1 + src/vs/workbench/workbench.web.main.ts | 1 + 8 files changed, 386 insertions(+), 288 deletions(-) create mode 100644 src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts rename src/vs/workbench/services/dialogs/browser/{remoteFileDialog.ts => simpleFileDialog.ts} (99%) create mode 100644 src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index a0de8d6da66..86750798cd7 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -37,7 +37,7 @@ import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remot import { IHostService } from 'vs/workbench/services/host/browser/host'; import { RemoteConnectionState, Deprecated_RemoteAuthorityContext, RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys'; import { IDownloadService } from 'vs/platform/download/common/download'; -import { OpenLocalFileFolderCommand, OpenLocalFileCommand, OpenLocalFolderCommand, SaveLocalFileCommand } from 'vs/workbench/services/dialogs/browser/remoteFileDialog'; +import { OpenLocalFileFolderCommand, OpenLocalFileCommand, OpenLocalFolderCommand, SaveLocalFileCommand } from 'vs/workbench/services/dialogs/browser/simpleFileDialog'; const WINDOW_ACTIONS_COMMAND_ID = 'remote.showActions'; const CLOSE_REMOTE_COMMAND_ID = 'remote.closeRemote'; diff --git a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts new file mode 100644 index 00000000000..06b23af90c0 --- /dev/null +++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts @@ -0,0 +1,194 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IWindowService, IURIToOpen, FileFilter } from 'vs/platform/windows/common/windows'; +import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { URI } from 'vs/base/common/uri'; +import { Schemas } from 'vs/base/common/network'; +import * as resources from 'vs/base/common/resources'; +import { IInstantiationService, } from 'vs/platform/instantiation/common/instantiation'; +import { SimpleFileDialog } from 'vs/workbench/services/dialogs/browser/simpleFileDialog'; +import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; +import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IFileService } from 'vs/platform/files/common/files'; +import { isWeb } from 'vs/base/common/platform'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; + +export class AbstractFileDialogService { + + _serviceBrand: undefined; + + constructor( + @IWindowService protected readonly windowService: IWindowService, + @IWorkspaceContextService protected readonly contextService: IWorkspaceContextService, + @IHistoryService protected readonly historyService: IHistoryService, + @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IConfigurationService protected readonly configurationService: IConfigurationService, + @IFileService protected readonly fileService: IFileService, + @IOpenerService protected readonly openerService: IOpenerService, + ) { } + + defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { + + // Check for last active file first... + let candidate = this.historyService.getLastActiveFile(schemeFilter); + + // ...then for last active file root + if (!candidate) { + candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); + } else { + candidate = candidate && resources.dirname(candidate); + } + + return candidate || undefined; + } + + defaultFolderPath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { + + // Check for last active file root first... + let candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); + + // ...then for last active file + if (!candidate) { + candidate = this.historyService.getLastActiveFile(schemeFilter); + } + + return candidate && resources.dirname(candidate) || undefined; + } + + defaultWorkspacePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { + + // Check for current workspace config file first... + if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { + const configuration = this.contextService.getWorkspace().configuration; + if (configuration && !isUntitledWorkspace(configuration, this.environmentService)) { + return resources.dirname(configuration) || undefined; + } + } + + // ...then fallback to default file path + return this.defaultFilePath(schemeFilter); + } + + protected addFileSchemaIfNeeded(schema: string): string[] { + // Include File schema unless the schema is web + // Don't allow untitled schema through. + if (isWeb) { + return schema === Schemas.untitled ? [Schemas.file] : [schema]; + } else { + return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]); + } + } + + protected async pickFileFolderAndOpenSimplified(schema: string, options: IPickAndOpenOptions, preferNewWindow: boolean): Promise { + const title = nls.localize('openFileOrFolder.title', 'Open File Or Folder'); + const availableFileSystems = this.addFileSchemaIfNeeded(schema); + + const uri = await this.pickResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); + + if (uri) { + const stat = await this.fileService.resolve(uri); + + const toOpen: IURIToOpen = stat.isDirectory ? { folderUri: uri } : { fileUri: uri }; + if (stat.isDirectory || options.forceNewWindow || preferNewWindow) { + return this.windowService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow }); + } else { + return this.openerService.open(uri); + } + } + } + + protected async pickFileAndOpenSimplified(schema: string, options: IPickAndOpenOptions, preferNewWindow: boolean): Promise { + const title = nls.localize('openFile.title', 'Open File'); + const availableFileSystems = this.addFileSchemaIfNeeded(schema); + + const uri = await this.pickResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); + if (uri) { + if (options.forceNewWindow || preferNewWindow) { + return this.windowService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow }); + } else { + return this.openerService.open(uri); + } + } + } + + protected async pickFolderAndOpenSimplified(schema: string, options: IPickAndOpenOptions): Promise { + const title = nls.localize('openFolder.title', 'Open Folder'); + const availableFileSystems = this.addFileSchemaIfNeeded(schema); + + const uri = await this.pickResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); + if (uri) { + return this.windowService.openWindow([{ folderUri: uri }], { forceNewWindow: options.forceNewWindow }); + } + } + + protected async pickWorkspaceAndOpenSimplified(schema: string, options: IPickAndOpenOptions): Promise { + const title = nls.localize('openWorkspace.title', 'Open Workspace'); + const filters: FileFilter[] = [{ name: nls.localize('filterName.workspace', 'Workspace'), extensions: [WORKSPACE_EXTENSION] }]; + const availableFileSystems = this.addFileSchemaIfNeeded(schema); + + const uri = await this.pickResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems }); + if (uri) { + return this.windowService.openWindow([{ workspaceUri: uri }], { forceNewWindow: options.forceNewWindow }); + } + } + + protected async pickFileToSaveSimplified(schema: string, options: ISaveDialogOptions): Promise { + if (!options.availableFileSystems) { + options.availableFileSystems = this.addFileSchemaIfNeeded(schema); + } + + options.title = nls.localize('saveFileAs.title', 'Save As'); + return this.saveRemoteResource(options); + } + + protected async showSaveDialogSimplified(schema: string, options: ISaveDialogOptions): Promise { + if (!options.availableFileSystems) { + options.availableFileSystems = this.addFileSchemaIfNeeded(schema); + } + + return this.saveRemoteResource(options); + } + + protected async showOpenDialogSimplified(schema: string, options: IOpenDialogOptions): Promise { + if (!options.availableFileSystems) { + options.availableFileSystems = this.addFileSchemaIfNeeded(schema); + } + + const uri = await this.pickResource(options); + + return uri ? [uri] : undefined; + } + + private pickResource(options: IOpenDialogOptions): Promise { + const simpleFileDialog = this.instantiationService.createInstance(SimpleFileDialog); + + return simpleFileDialog.showOpenDialog(options); + } + + private saveRemoteResource(options: ISaveDialogOptions): Promise { + const remoteFileDialog = this.instantiationService.createInstance(SimpleFileDialog); + + return remoteFileDialog.showSaveDialog(options); + } + + protected getSchemeFilterForWindow(): string { + return !this.environmentService.configuration.remoteAuthority ? Schemas.file : REMOTE_HOST_SCHEME; + } + + protected getFileSystemSchema(options: { availableFileSystems?: string[], defaultUri?: URI }): string { + return options.availableFileSystems && options.availableFileSystems[0] || this.getSchemeFilterForWindow(); + } +} + +function isUntitledWorkspace(path: URI, environmentService: IWorkbenchEnvironmentService): boolean { + return resources.isEqualOrParent(path, environmentService.untitledWorkspacesHome); +} diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index 4244aae656e..845e31adc9a 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -3,110 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; -import { IWindowService, INativeOpenDialogOptions, OpenDialogOptions, IURIToOpen, FileFilter, SaveDialogOptions } from 'vs/platform/windows/common/windows'; import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { URI } from 'vs/base/common/uri'; -import { Schemas } from 'vs/base/common/network'; -import * as resources from 'vs/base/common/resources'; -import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; -import { RemoteFileDialog } from 'vs/workbench/services/dialogs/browser/remoteFileDialog'; -import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IFileService } from 'vs/platform/files/common/files'; -import { isWeb } from 'vs/base/common/platform'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { AbstractFileDialogService } from 'vs/workbench/services/dialogs/browser/abstractFileDialogService'; -// TODO@Alex layer breaker -// tslint:disable-next-line: layering import-patterns -import { IElectronService } from 'vs/platform/electron/node/electron'; - -export class FileDialogService implements IFileDialogService { - - _serviceBrand: undefined; - - constructor( - @IWindowService private readonly windowService: IWindowService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IHistoryService private readonly historyService: IHistoryService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IFileService private readonly fileService: IFileService, - @IOpenerService private readonly openerService: IOpenerService, - @optional(IElectronService) private readonly electronService: IElectronService - ) { } - - defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { - - // Check for last active file first... - let candidate = this.historyService.getLastActiveFile(schemeFilter); - - // ...then for last active file root - if (!candidate) { - candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); - } else { - candidate = candidate && resources.dirname(candidate); - } - - return candidate || undefined; - } - - defaultFolderPath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { - - // Check for last active file root first... - let candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); - - // ...then for last active file - if (!candidate) { - candidate = this.historyService.getLastActiveFile(schemeFilter); - } - - return candidate && resources.dirname(candidate) || undefined; - } - - defaultWorkspacePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { - - // Check for current workspace config file first... - if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { - const configuration = this.contextService.getWorkspace().configuration; - if (configuration && !isUntitledWorkspace(configuration, this.environmentService)) { - return resources.dirname(configuration) || undefined; - } - } - - // ...then fallback to default file path - return this.defaultFilePath(schemeFilter); - } - - private toNativeOpenDialogOptions(options: IPickAndOpenOptions): INativeOpenDialogOptions { - return { - forceNewWindow: options.forceNewWindow, - telemetryExtraData: options.telemetryExtraData, - defaultPath: options.defaultUri && options.defaultUri.fsPath - }; - } - - private shouldUseSimplified(schema: string): { useSimplified: boolean, isSetting: boolean } { - const setting = (this.configurationService.getValue('files.simpleDialog.enable') === true); - - return { useSimplified: (schema !== Schemas.file) || setting, isSetting: (schema === Schemas.file) && setting }; - } - - private addFileSchemaIfNeeded(schema: string): string[] { - // Include File schema unless the schema is web - // Don't allow untitled schema through. - if (isWeb) { - return schema === Schemas.untitled ? [Schemas.file] : [schema]; - } else { - return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]); - } - } +export class FileDialogService extends AbstractFileDialogService implements IFileDialogService { async pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise { const schema = this.getFileSystemSchema(options); @@ -115,30 +17,7 @@ export class FileDialogService implements IFileDialogService { options.defaultUri = this.defaultFilePath(schema); } - const shouldUseSimplified = this.shouldUseSimplified(schema); - if (shouldUseSimplified.useSimplified) { - const title = nls.localize('openFileOrFolder.title', 'Open File Or Folder'); - const availableFileSystems = this.addFileSchemaIfNeeded(schema); - - const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); - - if (uri) { - const stat = await this.fileService.resolve(uri); - - const toOpen: IURIToOpen = stat.isDirectory ? { folderUri: uri } : { fileUri: uri }; - if (stat.isDirectory || options.forceNewWindow || shouldUseSimplified.isSetting) { - return this.windowService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow }); - } else { - return this.openerService.open(uri); - } - } - - return; - } - - if (this.electronService) { - return this.electronService.pickFileFolderAndOpen(this.toNativeOpenDialogOptions(options)); - } + return this.pickFileFolderAndOpenSimplified(schema, options, false); } async pickFileAndOpen(options: IPickAndOpenOptions): Promise { @@ -148,26 +27,7 @@ export class FileDialogService implements IFileDialogService { options.defaultUri = this.defaultFilePath(schema); } - const shouldUseSimplified = this.shouldUseSimplified(schema); - if (shouldUseSimplified.useSimplified) { - const title = nls.localize('openFile.title', 'Open File'); - const availableFileSystems = this.addFileSchemaIfNeeded(schema); - - const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); - if (uri) { - if (options.forceNewWindow || shouldUseSimplified.isSetting) { - return this.windowService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow }); - } else { - return this.openerService.open(uri); - } - } - - return; - } - - if (this.electronService) { - return this.electronService.pickFileAndOpen(this.toNativeOpenDialogOptions(options)); - } + return this.pickFileAndOpenSimplified(schema, options, false); } async pickFolderAndOpen(options: IPickAndOpenOptions): Promise { @@ -177,21 +37,7 @@ export class FileDialogService implements IFileDialogService { options.defaultUri = this.defaultFolderPath(schema); } - if (this.shouldUseSimplified(schema).useSimplified) { - const title = nls.localize('openFolder.title', 'Open Folder'); - const availableFileSystems = this.addFileSchemaIfNeeded(schema); - - const uri = await this.pickRemoteResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); - if (uri) { - return this.windowService.openWindow([{ folderUri: uri }], { forceNewWindow: options.forceNewWindow }); - } - - return; - } - - if (this.electronService) { - return this.electronService.pickFolderAndOpen(this.toNativeOpenDialogOptions(options)); - } + return this.pickFolderAndOpenSimplified(schema, options); } async pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise { @@ -201,143 +47,23 @@ export class FileDialogService implements IFileDialogService { options.defaultUri = this.defaultWorkspacePath(schema); } - if (this.shouldUseSimplified(schema).useSimplified) { - const title = nls.localize('openWorkspace.title', 'Open Workspace'); - const filters: FileFilter[] = [{ name: nls.localize('filterName.workspace', 'Workspace'), extensions: [WORKSPACE_EXTENSION] }]; - const availableFileSystems = this.addFileSchemaIfNeeded(schema); - - const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems }); - if (uri) { - return this.windowService.openWindow([{ workspaceUri: uri }], { forceNewWindow: options.forceNewWindow }); - } - - return; - } - - if (this.electronService) { - return this.electronService.pickWorkspaceAndOpen(this.toNativeOpenDialogOptions(options)); - } + return this.pickWorkspaceAndOpenSimplified(schema, options); } async pickFileToSave(options: ISaveDialogOptions): Promise { const schema = this.getFileSystemSchema(options); - if (this.shouldUseSimplified(schema).useSimplified) { - if (!options.availableFileSystems) { - options.availableFileSystems = this.addFileSchemaIfNeeded(schema); - } - - options.title = nls.localize('saveFileAs.title', 'Save As'); - return this.saveRemoteResource(options); - } - - if (this.electronService) { - const result = await this.electronService.showSaveDialog(this.toNativeSaveDialogOptions(options)); - if (result && !result.canceled && result.filePath) { - return URI.file(result.filePath); - } - } - - return; - } - - private toNativeSaveDialogOptions(options: ISaveDialogOptions): SaveDialogOptions { - options.defaultUri = options.defaultUri ? URI.file(options.defaultUri.path) : undefined; - return { - defaultPath: options.defaultUri && options.defaultUri.fsPath, - buttonLabel: options.saveLabel, - filters: options.filters, - title: options.title - }; + return this.pickFileToSaveSimplified(schema, options); } async showSaveDialog(options: ISaveDialogOptions): Promise { const schema = this.getFileSystemSchema(options); - if (this.shouldUseSimplified(schema).useSimplified) { - if (!options.availableFileSystems) { - options.availableFileSystems = this.addFileSchemaIfNeeded(schema); - } - - return this.saveRemoteResource(options); - } - - if (this.electronService) { - const result = await this.electronService.showSaveDialog(this.toNativeSaveDialogOptions(options)); - if (result && !result.canceled && result.filePath) { - return URI.file(result.filePath); - } - } - - return; + return this.showSaveDialogSimplified(schema, options); } async showOpenDialog(options: IOpenDialogOptions): Promise { const schema = this.getFileSystemSchema(options); - if (this.shouldUseSimplified(schema).useSimplified) { - if (!options.availableFileSystems) { - options.availableFileSystems = this.addFileSchemaIfNeeded(schema); - } - - const uri = await this.pickRemoteResource(options); - - return uri ? [uri] : undefined; - } - - const defaultUri = options.defaultUri; - - const newOptions: OpenDialogOptions = { - title: options.title, - defaultPath: defaultUri && defaultUri.fsPath, - buttonLabel: options.openLabel, - filters: options.filters, - properties: [] - }; - - newOptions.properties!.push('createDirectory'); - - if (options.canSelectFiles) { - newOptions.properties!.push('openFile'); - } - - if (options.canSelectFolders) { - newOptions.properties!.push('openDirectory'); - } - - if (options.canSelectMany) { - newOptions.properties!.push('multiSelections'); - } - - if (this.electronService) { - const result = await this.electronService.showOpenDialog(newOptions); - - return result && Array.isArray(result.filePaths) && result.filePaths.length > 0 ? result.filePaths.map(URI.file) : undefined; - } - - return; - } - - private pickRemoteResource(options: IOpenDialogOptions): Promise { - const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog); - - return remoteFileDialog.showOpenDialog(options); - } - - private saveRemoteResource(options: ISaveDialogOptions): Promise { - const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog); - - return remoteFileDialog.showSaveDialog(options); + return this.showOpenDialogSimplified(schema, options); } - - private getSchemeFilterForWindow(): string { - return !this.environmentService.configuration.remoteAuthority ? Schemas.file : REMOTE_HOST_SCHEME; - } - - private getFileSystemSchema(options: { availableFileSystems?: string[], defaultUri?: URI }): string { - return options.availableFileSystems && options.availableFileSystems[0] || this.getSchemeFilterForWindow(); - } -} - -function isUntitledWorkspace(path: URI, environmentService: IWorkbenchEnvironmentService): boolean { - return resources.isEqualOrParent(path, environmentService.untitledWorkspacesHome); } registerSingleton(IFileDialogService, FileDialogService, true); diff --git a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts similarity index 99% rename from src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts rename to src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index d042a953970..ee8b8925e04 100644 --- a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -98,7 +98,7 @@ enum UpdateResult { InvalidPath } -export class RemoteFileDialog { +export class SimpleFileDialog { private options!: IOpenDialogOptions; private currentFolder!: URI; private filePickBox!: IQuickPick; @@ -303,7 +303,7 @@ export class RemoteFileDialog { this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length]; this.filePickBox.items = []; - function doResolve(dialog: RemoteFileDialog, uri: URI | undefined) { + function doResolve(dialog: SimpleFileDialog, uri: URI | undefined) { if (uri) { uri = resources.removeTrailingPathSeparator(uri); } @@ -335,7 +335,7 @@ export class RemoteFileDialog { } }); - function handleAccept(dialog: RemoteFileDialog) { + function handleAccept(dialog: SimpleFileDialog) { if (dialog.busy) { // Save the accept until the file picker is not busy. dialog.onBusyChangeEmitter.event((busy: boolean) => { diff --git a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts new file mode 100644 index 00000000000..43c93906db0 --- /dev/null +++ b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts @@ -0,0 +1,177 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWindowService, OpenDialogOptions, SaveDialogOptions, INativeOpenDialogOptions } from 'vs/platform/windows/common/windows'; +import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { URI } from 'vs/base/common/uri'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { AbstractFileDialogService } from 'vs/workbench/services/dialogs/browser/abstractFileDialogService'; +import { Schemas } from 'vs/base/common/network'; + +export class FileDialogService extends AbstractFileDialogService implements IFileDialogService { + + _serviceBrand: undefined; + + constructor( + @IWindowService windowService: IWindowService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IHistoryService historyService: IHistoryService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IInstantiationService instantiationService: IInstantiationService, + @IConfigurationService configurationService: IConfigurationService, + @IFileService fileService: IFileService, + @IOpenerService openerService: IOpenerService, + @IElectronService private readonly electronService: IElectronService + ) { super(windowService, contextService, historyService, environmentService, instantiationService, configurationService, fileService, openerService); } + + private toNativeOpenDialogOptions(options: IPickAndOpenOptions): INativeOpenDialogOptions { + return { + forceNewWindow: options.forceNewWindow, + telemetryExtraData: options.telemetryExtraData, + defaultPath: options.defaultUri && options.defaultUri.fsPath + }; + } + + private shouldUseSimplified(schema: string): { useSimplified: boolean, isSetting: boolean } { + const setting = (this.configurationService.getValue('files.simpleDialog.enable') === true); + + return { useSimplified: (schema !== Schemas.file) || setting, isSetting: (schema === Schemas.file) && setting }; + } + + async pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise { + const schema = this.getFileSystemSchema(options); + + if (!options.defaultUri) { + options.defaultUri = this.defaultFilePath(schema); + } + + const shouldUseSimplified = this.shouldUseSimplified(schema); + if (shouldUseSimplified.useSimplified) { + return this.pickFileFolderAndOpenSimplified(schema, options, shouldUseSimplified.isSetting); + } + return this.electronService.pickFileFolderAndOpen(this.toNativeOpenDialogOptions(options)); + } + + async pickFileAndOpen(options: IPickAndOpenOptions): Promise { + const schema = this.getFileSystemSchema(options); + + if (!options.defaultUri) { + options.defaultUri = this.defaultFilePath(schema); + } + + const shouldUseSimplified = this.shouldUseSimplified(schema); + if (shouldUseSimplified.useSimplified) { + return this.pickFileAndOpenSimplified(schema, options, shouldUseSimplified.isSetting); + } + return this.electronService.pickFileAndOpen(this.toNativeOpenDialogOptions(options)); + } + + async pickFolderAndOpen(options: IPickAndOpenOptions): Promise { + const schema = this.getFileSystemSchema(options); + + if (!options.defaultUri) { + options.defaultUri = this.defaultFolderPath(schema); + } + + if (this.shouldUseSimplified(schema).useSimplified) { + return this.pickFolderAndOpenSimplified(schema, options); + } + return this.electronService.pickFolderAndOpen(this.toNativeOpenDialogOptions(options)); + } + + async pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise { + const schema = this.getFileSystemSchema(options); + + if (!options.defaultUri) { + options.defaultUri = this.defaultWorkspacePath(schema); + } + + if (this.shouldUseSimplified(schema).useSimplified) { + return this.pickWorkspaceAndOpenSimplified(schema, options); + } + return this.electronService.pickWorkspaceAndOpen(this.toNativeOpenDialogOptions(options)); + } + + async pickFileToSave(options: ISaveDialogOptions): Promise { + const schema = this.getFileSystemSchema(options); + if (this.shouldUseSimplified(schema).useSimplified) { + return this.pickFileToSaveSimplified(schema, options); + } else { + const result = await this.electronService.showSaveDialog(this.toNativeSaveDialogOptions(options)); + if (result && !result.canceled && result.filePath) { + return URI.file(result.filePath); + } + } + return; + } + + private toNativeSaveDialogOptions(options: ISaveDialogOptions): SaveDialogOptions { + options.defaultUri = options.defaultUri ? URI.file(options.defaultUri.path) : undefined; + return { + defaultPath: options.defaultUri && options.defaultUri.fsPath, + buttonLabel: options.saveLabel, + filters: options.filters, + title: options.title + }; + } + + async showSaveDialog(options: ISaveDialogOptions): Promise { + const schema = this.getFileSystemSchema(options); + if (this.shouldUseSimplified(schema).useSimplified) { + return this.showSaveDialogSimplified(schema, options); + } + + const result = await this.electronService.showSaveDialog(this.toNativeSaveDialogOptions(options)); + if (result && !result.canceled && result.filePath) { + return URI.file(result.filePath); + } + + return; + } + + async showOpenDialog(options: IOpenDialogOptions): Promise { + const schema = this.getFileSystemSchema(options); + if (this.shouldUseSimplified(schema).useSimplified) { + return this.showOpenDialogSimplified(schema, options); + } + + const defaultUri = options.defaultUri; + + const newOptions: OpenDialogOptions = { + title: options.title, + defaultPath: defaultUri && defaultUri.fsPath, + buttonLabel: options.openLabel, + filters: options.filters, + properties: [] + }; + + newOptions.properties!.push('createDirectory'); + + if (options.canSelectFiles) { + newOptions.properties!.push('openFile'); + } + + if (options.canSelectFolders) { + newOptions.properties!.push('openDirectory'); + } + + if (options.canSelectMany) { + newOptions.properties!.push('multiSelections'); + } + + const result = await this.electronService.showOpenDialog(newOptions); + return result && Array.isArray(result.filePaths) && result.filePaths.length > 0 ? result.filePaths.map(URI.file) : undefined; + } +} + +registerSingleton(IFileDialogService, FileDialogService, true); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 6086bc3cd75..b6a1c8d8577 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -65,7 +65,6 @@ import 'vs/workbench/services/editor/browser/codeEditorService'; import 'vs/workbench/services/preferences/browser/preferencesService'; import 'vs/workbench/services/configuration/common/jsonEditingService'; import 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; -import 'vs/workbench/services/dialogs/browser/fileDialogService'; import 'vs/workbench/services/editor/browser/editorService'; import 'vs/workbench/services/history/browser/history'; import 'vs/workbench/services/activity/browser/activityService'; diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index e84412fb8ec..66904df5fa3 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -27,6 +27,7 @@ import 'vs/workbench/electron-browser/desktop.main'; //#region --- workbench services +import 'vs/workbench/services/dialogs/electron-browser/fileDialogService'; import 'vs/workbench/services/integrity/node/integrityService'; import 'vs/workbench/services/textMate/electron-browser/textMateService'; import 'vs/workbench/services/search/node/searchService'; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 537b28b7967..86e9d4f1f5b 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -42,6 +42,7 @@ import 'vs/workbench/services/update/browser/updateService'; import 'vs/workbench/contrib/stats/browser/workspaceStatsService'; import 'vs/workbench/services/workspace/browser/workspacesService'; import 'vs/workbench/services/dialogs/browser/dialogService'; +import 'vs/workbench/services/dialogs/browser/fileDialogService'; import 'vs/workbench/services/host/browser/browserHostService'; import 'vs/workbench/services/request/browser/requestService'; import 'vs/workbench/browser/web.simpleservices'; -- GitLab