diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index a1b43d7785ffb08aad723670bff772b933d11819..b988725db1370be64d3d16e0bd3fecfe6cf235b9 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -10,6 +10,8 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { URI } from 'vs/base/common/uri'; import { basename } from 'vs/base/common/paths'; import { localize } from 'vs/nls'; +import { FileFilter } from 'vs/platform/windows/common/windows'; +import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; export interface IConfirmation { title?: string; @@ -39,6 +41,74 @@ export interface IConfirmationResult { checkboxChecked?: boolean; } +export interface IPickAndOpenOptions { + forceNewWindow?: boolean; + defaultUri?: URI; + telemetryExtraData?: ITelemetryData; +} + +export interface ISaveDialogOptions { + /** + * A human-readable string for the dialog title + */ + title?: string; + + /** + * The resource the dialog shows when opened. + */ + defaultUri?: URI; + + /** + * A set of file filters that are used by the dialog. Each entry is a human readable label, + * like "TypeScript", and an array of extensions. + */ + filters?: FileFilter[]; + + /** + * A human-readable string for the ok button + */ + buttonLabel?: string; +} + +export interface IOpenDialogOptions { + /** + * A human-readable string for the dialog title + */ + title?: string; + + /** + * The resource the dialog shows when opened. + */ + defaultUri?: URI; + + /** + * A human-readable string for the open button. + */ + openLabel?: string; + + /** + * Allow to select files, defaults to `true`. + */ + canSelectFiles?: boolean; + + /** + * Allow to select folders, defaults to `false`. + */ + canSelectFolders?: boolean; + + /** + * Allow to select many files or folders. + */ + canSelectMany?: boolean; + + /** + * A set of file filters that are used by the dialog. Each entry is a human readable label, + * like "TypeScript", and an array of extensions. + */ + filters?: FileFilter[]; +} + + export const IDialogService = createDecorator('dialogService'); export interface IDialogOptions { @@ -71,6 +141,66 @@ export interface IDialogService { show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): TPromise; } + +export const IFileDialogService = createDecorator('fileDialogService'); + +/** + * A service to bring up file dialogs. + */ +export interface IFileDialogService { + + _serviceBrand: any; + + /** + * The default path for a new file based on previously used files. + * @param schemeFilter The scheme of the file path. + */ + defaultFilePath(schemeFilter: string): URI; + + /** + * The default path for a new folder based on previously used folders. + * @param schemeFilter The scheme of the folder path. + */ + defaultFolderPath(schemeFilter: string): URI; + + /** + * The default path for a new workspace based on previously used workspaces. + * @param schemeFilter The scheme of the workspace path. + */ + defaultWorkspacePath(schemeFilter: string): URI; + + /** + * Shows a file-folder selection dialog and opens the selected entry. + */ + pickFileFolderAndOpen(options: IPickAndOpenOptions): TPromise; + + /** + * Shows a file selection dialog and opens the selected entry. + */ + pickFileAndOpen(options: IPickAndOpenOptions): TPromise; + + /** + * Shows a folder selection dialog and opens the selected entry. + */ + pickFolderAndOpen(options: IPickAndOpenOptions): TPromise; + + /** + * Shows a workspace selection dialog and opens the selected entry. + */ + pickWorkspaceAndOpen(options: IPickAndOpenOptions): TPromise; + + /** + * Shows a save file dialog and returns the chosen file URI. + */ + showSaveDialog(options: ISaveDialogOptions): TPromise; + + /** + * Shows a open file dialog and returns the chosen file URI. + */ + showOpenDialog(options: IOpenDialogOptions): TPromise; + +} + const MAX_CONFIRM_FILES = 10; export function getConfirmMessage(start: string, resourcesToConfirm: URI[]): string { const message = [start]; diff --git a/src/vs/platform/workbench/common/contextkeys.ts b/src/vs/platform/workbench/common/contextkeys.ts index d513b3e6c91ee4eb40f3219b5d518b46f2bef50f..c911d0ddbaf66dde6fbe8248f33ffe3f8286f276 100644 --- a/src/vs/platform/workbench/common/contextkeys.ts +++ b/src/vs/platform/workbench/common/contextkeys.ts @@ -10,7 +10,6 @@ import { isMacintosh, isLinux, isWindows } from 'vs/base/common/platform'; export const InputFocusedContextKey = 'inputFocus'; export const InputFocusedContext = new RawContextKey(InputFocusedContextKey, false); -export const FileDialogContext = new RawContextKey('fileDialog', 'local'); export const IsMacContext = new RawContextKey('isMac', isMacintosh); export const IsLinuxContext = new RawContextKey('isLinux', isLinux); export const IsWindowsContext = new RawContextKey('isWindows', isWindows); diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index b7b97385cda76eb27507abbbf49a28cfb3dce757..6ca2a379597edbd1acb431a36eb9e21607fb335a 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -13,14 +13,13 @@ import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { WORKSPACE_FILTER, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL, PICK_WORKSPACE_FOLDER_COMMAND_ID, defaultWorkspacePath, defaultFilePath, defaultFolderPath } from 'vs/workbench/browser/actions/workspaceCommands'; +import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL, PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; +import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; export class OpenFileAction extends Action { @@ -30,16 +29,13 @@ export class OpenFileAction extends Action { constructor( id: string, label: string, - @IWindowService private windowService: IWindowService, - @IHistoryService private historyService: IHistoryService, - @IWorkspaceContextService private contextService: IWorkspaceContextService + @IFileDialogService private dialogService: IFileDialogService ) { super(id, label); } run(event?: any, data?: ITelemetryData): TPromise { - const defaultPathURI = defaultFilePath(this.contextService, this.historyService, Schemas.file); - return this.windowService.pickFileAndOpen({ telemetryExtraData: data, dialogOptions: { defaultPath: defaultPathURI && defaultPathURI.fsPath } }); + return this.dialogService.pickFileAndOpen({ forceNewWindow: false, telemetryExtraData: data }); } } @@ -51,16 +47,13 @@ export class OpenFolderAction extends Action { constructor( id: string, label: string, - @IWindowService private windowService: IWindowService, - @IHistoryService private historyService: IHistoryService, - @IWorkspaceContextService private contextService: IWorkspaceContextService + @IFileDialogService private dialogService: IFileDialogService ) { super(id, label); } run(event?: any, data?: ITelemetryData): TPromise { - const defaultPathURI = defaultFolderPath(this.contextService, this.historyService, Schemas.file); - return this.windowService.pickFolderAndOpen({ telemetryExtraData: data, dialogOptions: { defaultPath: defaultPathURI && defaultPathURI.fsPath } }); + return this.dialogService.pickFolderAndOpen({ forceNewWindow: false, telemetryExtraData: data }); } } @@ -72,16 +65,13 @@ export class OpenFileFolderAction extends Action { constructor( id: string, label: string, - @IWindowService private windowService: IWindowService, - @IHistoryService private historyService: IHistoryService, - @IWorkspaceContextService private contextService: IWorkspaceContextService + @IFileDialogService private dialogService: IFileDialogService ) { super(id, label); } run(event?: any, data?: ITelemetryData): TPromise { - const defaultPathURI = defaultFilePath(this.contextService, this.historyService, Schemas.file); - return this.windowService.pickFileFolderAndOpen({ telemetryExtraData: data, dialogOptions: { defaultPath: defaultPathURI && defaultPathURI.fsPath } }); + return this.dialogService.pickFileFolderAndOpen({ forceNewWindow: false, telemetryExtraData: data }); } } @@ -144,18 +134,18 @@ export class SaveWorkspaceAsAction extends Action { constructor( id: string, label: string, - @IWindowService private windowService: IWindowService, - @IEnvironmentService private environmentService: IEnvironmentService, @IWorkspaceContextService private contextService: IWorkspaceContextService, @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService, - @IHistoryService private historyService: IHistoryService + @IFileDialogService private dialogService: IFileDialogService + ) { super(id, label); } run(): TPromise { - return this.getNewWorkspaceConfigPath().then(configPath => { - if (configPath) { + return this.getNewWorkspaceConfigPath().then(configPathUri => { + if (configPathUri) { + const configPath = configPathUri.fsPath; switch (this.contextService.getWorkbenchState()) { case WorkbenchState.EMPTY: case WorkbenchState.FOLDER: @@ -171,13 +161,12 @@ export class SaveWorkspaceAsAction extends Action { }); } - private getNewWorkspaceConfigPath(): TPromise { - const defaultPathURI = defaultWorkspacePath(this.contextService, this.historyService, this.environmentService, Schemas.file); - return this.windowService.showSaveDialog({ + private getNewWorkspaceConfigPath(): TPromise { + return this.dialogService.showSaveDialog({ buttonLabel: mnemonicButtonLabel(nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")), title: nls.localize('saveWorkspace', "Save Workspace"), filters: WORKSPACE_FILTER, - defaultPath: defaultPathURI && defaultPathURI.fsPath + defaultUri: this.dialogService.defaultWorkspacePath(Schemas.file) }); } } @@ -190,17 +179,13 @@ export class OpenWorkspaceAction extends Action { constructor( id: string, label: string, - @IWindowService private windowService: IWindowService, - @IWorkspaceContextService private contextService: IWorkspaceContextService, - @IHistoryService private historyService: IHistoryService, - @IEnvironmentService private environmentService: IEnvironmentService + @IFileDialogService private dialogService: IFileDialogService ) { super(id, label); } run(event?: any, data?: ITelemetryData): TPromise { - const defaultPathURI = defaultWorkspacePath(this.contextService, this.historyService, this.environmentService, Schemas.file); - return this.windowService.pickWorkspaceAndOpen({ telemetryExtraData: data, dialogOptions: { defaultPath: defaultPathURI && defaultPathURI.fsPath } }); + return this.dialogService.pickWorkspaceAndOpen({ telemetryExtraData: data }); } } diff --git a/src/vs/workbench/browser/actions/workspaceCommands.ts b/src/vs/workbench/browser/actions/workspaceCommands.ts index 8f13c774095dc1fe4d56c0331cb0590cb1fe8d63..954543e8e5fc29b4ee2f6f58e9cb0e6ebc257435 100644 --- a/src/vs/workbench/browser/actions/workspaceCommands.ts +++ b/src/vs/workbench/browser/actions/workspaceCommands.ts @@ -5,142 +5,54 @@ 'use strict'; -import { TPromise } from 'vs/base/common/winjs.base'; import * as nls from 'vs/nls'; -import { IWindowService } from 'vs/platform/windows/common/windows'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; -import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { CancellationToken } from 'vs/base/common/cancellation'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { FileKind, isParent } from 'vs/platform/files/common/files'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { FileKind } from 'vs/platform/files/common/files'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { isLinux } from 'vs/base/common/platform'; import { ILabelService } from 'vs/platform/label/common/label'; import { IQuickInputService, IPickOptions, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { getIconClasses } from 'vs/workbench/browser/labels'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { Schemas } from 'vs/base/common/network'; +import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; export const ADD_ROOT_FOLDER_COMMAND_ID = 'addRootFolder'; export const ADD_ROOT_FOLDER_LABEL = nls.localize('addFolderToWorkspace', "Add Folder to Workspace..."); export const PICK_WORKSPACE_FOLDER_COMMAND_ID = '_workbench.pickWorkspaceFolder'; -function pickFolders(buttonLabel: string, title: string, windowService: IWindowService, contextService: IWorkspaceContextService, historyService: IHistoryService): TPromise { - const defaultPathURI = defaultFolderPath(contextService, historyService, Schemas.file); - return windowService.showOpenDialog({ - buttonLabel, - title, - properties: ['multiSelections', 'openDirectory', 'createDirectory'], - defaultPath: defaultPathURI && defaultPathURI.fsPath - }); -} - -export function defaultFolderPath(contextService: IWorkspaceContextService, historyService: IHistoryService, schemeFilter: string): URI { - let candidate: URI; - - // Check for last active file root first... - candidate = historyService.getLastActiveWorkspaceRoot(schemeFilter); - - // ...then for last active file - if (!candidate) { - candidate = historyService.getLastActiveFile(schemeFilter); - } - - return candidate ? resources.dirname(candidate) : void 0; -} - - -function services(accessor: ServicesAccessor): { windowService: IWindowService, historyService: IHistoryService, contextService: IWorkspaceContextService, environmentService: IEnvironmentService } { - return { - windowService: accessor.get(IWindowService), - historyService: accessor.get(IHistoryService), - contextService: accessor.get(IWorkspaceContextService), - environmentService: accessor.get(IEnvironmentService) - }; -} - -export function defaultFilePath(contextService: IWorkspaceContextService, historyService: IHistoryService, schemeFilter: string): URI { - let candidate: URI; - - // Check for last active file first... - candidate = historyService.getLastActiveFile(schemeFilter); - - // ...then for last active file root - if (!candidate) { - candidate = historyService.getLastActiveWorkspaceRoot(schemeFilter); - } - - return candidate ? resources.dirname(candidate) : void 0; -} - -export function defaultWorkspacePath(contextService: IWorkspaceContextService, historyService: IHistoryService, environmentService: IEnvironmentService, schemeFilter: string): URI { - - // Check for current workspace config file first... - if (schemeFilter === Schemas.file && contextService.getWorkbenchState() === WorkbenchState.WORKSPACE && !isUntitledWorkspace(contextService.getWorkspace().configuration.fsPath, environmentService)) { - return resources.dirname(contextService.getWorkspace().configuration); - } - - // ...then fallback to default folder path - return defaultFolderPath(contextService, historyService, schemeFilter); -} - -function isUntitledWorkspace(path: string, environmentService: IEnvironmentService): boolean { - return isParent(path, environmentService.workspacesHome, !isLinux /* ignore case */); -} - // Command registration CommandsRegistry.registerCommand({ id: 'workbench.action.files.openFileFolderInNewWindow', - handler: (accessor: ServicesAccessor) => { - const { windowService, historyService, contextService } = services(accessor); - const defaultPathURI = defaultFilePath(contextService, historyService, Schemas.file); - windowService.pickFileFolderAndOpen({ forceNewWindow: true, dialogOptions: { defaultPath: defaultPathURI && defaultPathURI.fsPath } }); - } + handler: (accessor: ServicesAccessor) => accessor.get(IFileDialogService).pickFileFolderAndOpen({ forceNewWindow: true }) }); CommandsRegistry.registerCommand({ id: '_files.pickFolderAndOpen', - handler: (accessor: ServicesAccessor, forceNewWindow: boolean) => { - const { windowService, historyService, contextService } = services(accessor); - const defaultPathURI = defaultFolderPath(contextService, historyService, Schemas.file); - windowService.pickFolderAndOpen({ forceNewWindow, dialogOptions: { defaultPath: defaultPathURI && defaultPathURI.fsPath } }); - } + handler: (accessor: ServicesAccessor, forceNewWindow: boolean) => accessor.get(IFileDialogService).pickFolderAndOpen({ forceNewWindow }) }); CommandsRegistry.registerCommand({ id: 'workbench.action.files.openFolderInNewWindow', - handler: (accessor: ServicesAccessor) => { - const { windowService, historyService, contextService } = services(accessor); - const defaultPathURI = defaultFolderPath(contextService, historyService, Schemas.file); - windowService.pickFolderAndOpen({ forceNewWindow: true, dialogOptions: { defaultPath: defaultPathURI && defaultPathURI.fsPath } }); - } + handler: (accessor: ServicesAccessor) => accessor.get(IFileDialogService).pickFolderAndOpen({ forceNewWindow: true }) }); CommandsRegistry.registerCommand({ id: 'workbench.action.files.openFileInNewWindow', - handler: (accessor: ServicesAccessor) => { - const { windowService, historyService, contextService } = services(accessor); - const defaultPathURI = defaultFilePath(contextService, historyService, Schemas.file); - windowService.pickFileAndOpen({ forceNewWindow: true, dialogOptions: { defaultPath: defaultPathURI && defaultPathURI.fsPath } }); - } + handler: (accessor: ServicesAccessor) => accessor.get(IFileDialogService).pickFileAndOpen({ forceNewWindow: true }) }); CommandsRegistry.registerCommand({ id: 'workbench.action.openWorkspaceInNewWindow', - handler: (accessor: ServicesAccessor) => { - const { windowService, historyService, contextService, environmentService } = services(accessor); - const defaultPathURI = defaultWorkspacePath(contextService, historyService, environmentService, Schemas.file); - windowService.pickWorkspaceAndOpen({ forceNewWindow: true, dialogOptions: { defaultPath: defaultPathURI && defaultPathURI.fsPath } }); - } + handler: (accessor: ServicesAccessor) => accessor.get(IFileDialogService).pickWorkspaceAndOpen({ forceNewWindow: true }) }); CommandsRegistry.registerCommand({ @@ -148,17 +60,23 @@ CommandsRegistry.registerCommand({ handler: (accessor) => { const viewletService = accessor.get(IViewletService); const workspaceEditingService = accessor.get(IWorkspaceEditingService); - return pickFolders(mnemonicButtonLabel(nls.localize({ key: 'add', comment: ['&& denotes a mnemonic'] }, "&&Add")), nls.localize('addFolderToWorkspaceTitle', "Add Folder to Workspace"), - accessor.get(IWindowService), accessor.get(IWorkspaceContextService), accessor.get(IHistoryService)).then(folders => { - if (!folders || !folders.length) { - return null; - } - - // Add and show Files Explorer viewlet - return workspaceEditingService.addFolders(folders.map(folder => ({ uri: URI.file(folder) }))) - .then(() => viewletService.openViewlet(viewletService.getDefaultViewletId(), true)) - .then(() => void 0); - }); + const dialogsService = accessor.get(IFileDialogService); + return dialogsService.showOpenDialog({ + openLabel: mnemonicButtonLabel(nls.localize({ key: 'add', comment: ['&& denotes a mnemonic'] }, "&&Add")), + title: nls.localize('addFolderToWorkspaceTitle', "Add Folder to Workspace"), + canSelectFolders: true, + canSelectMany: true, + defaultUri: dialogsService.defaultFolderPath(Schemas.file) + }).then(folders => { + if (!folders || !folders.length) { + return null; + } + + // Add and show Files Explorer viewlet + return workspaceEditingService.addFolders(folders.map(folder => ({ uri: folder }))) + .then(() => viewletService.openViewlet(viewletService.getDefaultViewletId(), true)) + .then(() => void 0); + }); } }); diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index 196f0b30ab5eb53dce952fff307efb296dac419d..298d2f13ecab3ef960f41f76561f5d7c85003443 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -23,7 +23,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ADD_ROOT_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; -import { FileDialogContext, IsMacContext } from 'vs/platform/workbench/common/contextkeys'; +import { IsMacContext } from 'vs/platform/workbench/common/contextkeys'; // Contribute Commands registerCommands(); @@ -39,12 +39,11 @@ workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(Switch workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(QuickSwitchWindow, QuickSwitchWindow.ID, QuickSwitchWindow.LABEL), 'Quick Switch Window...'); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenRecentAction, QuickOpenRecentAction.ID, QuickOpenRecentAction.LABEL), 'File: Quick Open Recent...', fileCategory); -const isLocal = FileDialogContext.isEqualTo('local'); if (isMacintosh) { - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }, isLocal), 'File: Open...', fileCategory, isLocal); + workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open...', fileCategory); } else { - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }, isLocal), 'File: Open File...', fileCategory, isLocal); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }, isLocal), 'File: Open Folder...', fileCategory, isLocal); + workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open File...', fileCategory); + workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }), 'File: Open Folder...', fileCategory); } workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(CloseWorkspaceAction, CloseWorkspaceAction.ID, CloseWorkspaceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'File: Close Workspace', fileCategory); @@ -108,10 +107,10 @@ workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(Increa workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(DecreaseViewSizeAction, DecreaseViewSizeAction.ID, DecreaseViewSizeAction.LABEL, null), 'View: Decrease Current View Size', viewCategory); const workspacesCategory = nls.localize('workspaces', "Workspaces"); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL), 'Workspaces: Add Folder to Workspace...', workspacesCategory, isLocal); +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL), 'Workspaces: Add Folder to Workspace...', workspacesCategory); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(GlobalRemoveRootFolderAction, GlobalRemoveRootFolderAction.ID, GlobalRemoveRootFolderAction.LABEL), 'Workspaces: Remove Folder from Workspace...', workspacesCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceAction, OpenWorkspaceAction.ID, OpenWorkspaceAction.LABEL), 'Workspaces: Open Workspace...', workspacesCategory, isLocal); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SaveWorkspaceAsAction, SaveWorkspaceAsAction.ID, SaveWorkspaceAsAction.LABEL), 'Workspaces: Save Workspace As...', workspacesCategory, isLocal); +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceAction, OpenWorkspaceAction.ID, OpenWorkspaceAction.LABEL), 'Workspaces: Open Workspace...', workspacesCategory); +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SaveWorkspaceAsAction, SaveWorkspaceAsAction.ID, SaveWorkspaceAsAction.LABEL), 'Workspaces: Save Workspace As...', workspacesCategory); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(DuplicateWorkspaceInNewWindowAction, DuplicateWorkspaceInNewWindowAction.ID, DuplicateWorkspaceInNewWindowAction.LABEL), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory); CommandsRegistry.registerCommand(OpenWorkspaceConfigFileAction.ID, serviceAccessor => { @@ -171,7 +170,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { title: nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File...") }, order: 1, - when: ContextKeyExpr.and(IsMacContext.toNegated(), isLocal) + when: IsMacContext.toNegated() }); MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { @@ -181,7 +180,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { title: nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...") }, order: 2, - when: ContextKeyExpr.and(IsMacContext.toNegated(), isLocal) + when: IsMacContext.toNegated() }); MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { @@ -191,7 +190,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { title: nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...") }, order: 1, - when: ContextKeyExpr.and(IsMacContext, isLocal) + when: IsMacContext }); MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { @@ -200,8 +199,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { id: OpenWorkspaceAction.ID, title: nls.localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "Open Wor&&kspace...") }, - order: 3, - when: isLocal + order: 3 }); MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { @@ -228,8 +226,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { id: ADD_ROOT_FOLDER_COMMAND_ID, title: nls.localize({ key: 'miAddFolderToWorkspace', comment: ['&& denotes a mnemonic'] }, "A&&dd Folder to Workspace...") }, - order: 1, - when: isLocal + order: 1 }); MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { @@ -238,8 +235,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { id: SaveWorkspaceAsAction.ID, title: nls.localize('miSaveWorkspaceAs', "Save Workspace As...") }, - order: 2, - when: isLocal + order: 2 }); MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index 04474dc883cbe56dd12b25fc12898f7bbda06caf..52db898a2aab7aa6978354952a4b994a66fff37d 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -93,7 +93,7 @@ import { IDecorationsService } from 'vs/workbench/services/decorations/browser/d import { ActivityService } from 'vs/workbench/services/activity/browser/activityService'; import { URI } from 'vs/base/common/uri'; import { IListService, ListService } from 'vs/platform/list/browser/listService'; -import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, FileDialogContext } from 'vs/platform/workbench/common/contextkeys'; +import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext } from 'vs/platform/workbench/common/contextkeys'; import { IViewsService } from 'vs/workbench/common/views'; import { ViewsService } from 'vs/workbench/browser/parts/views/views'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -115,6 +115,8 @@ import { ContextViewService } from 'vs/platform/contextview/browser/contextViewS import { WorkbenchThemeService } from 'vs/workbench/services/themes/electron-browser/workbenchThemeService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { LabelService, ILabelService } from 'vs/platform/label/common/label'; +import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { FileDialogService } from 'vs/workbench/services/dialogs/electron-browser/dialogService'; interface WorkbenchParams { configuration: IWindowConfiguration; @@ -415,6 +417,9 @@ export class Workbench extends Disposable implements IPartService { // History serviceCollection.set(IHistoryService, new SyncDescriptor(HistoryService)); + // File Dialogs + serviceCollection.set(IFileDialogService, new SyncDescriptor(FileDialogService)); + // Backup File Service if (this.workbenchParams.configuration.backupPath) { this.backupFileService = this.instantiationService.createInstance(BackupFileService, this.workbenchParams.configuration.backupPath); @@ -613,7 +618,6 @@ export class Workbench extends Disposable implements IPartService { IsMacContext.bindTo(this.contextKeyService); IsLinuxContext.bindTo(this.contextKeyService); IsWindowsContext.bindTo(this.contextKeyService); - FileDialogContext.bindTo(this.contextKeyService); const sidebarVisibleContextRaw = new RawContextKey('sidebarVisible', false); this.sideBarVisibleContext = sidebarVisibleContextRaw.bindTo(this.contextKeyService); diff --git a/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts b/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts index 30e273b8e62b1ce3c4eb76e07cf71943a2b6b9d2..360bf07b1f9b6a6aed390537d25d71ec09aff95a 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts @@ -25,7 +25,6 @@ import { ResourceContextKey } from 'vs/workbench/common/resources'; import { WorkbenchListDoubleSelection } from 'vs/platform/list/browser/listService'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { FileDialogContext } from 'vs/platform/workbench/common/contextkeys'; // Contribute Global Actions const category = nls.localize('filesCategory', "File"); @@ -184,7 +183,7 @@ appendToCommandPalette(SAVE_FILES_COMMAND_ID, { value: nls.localize('saveFiles', appendToCommandPalette(REVERT_FILE_COMMAND_ID, { value: nls.localize('revert', "Revert File"), original: 'File: Revert File' }, category); appendToCommandPalette(COMPARE_WITH_SAVED_COMMAND_ID, { value: nls.localize('compareActiveWithSaved', "Compare Active File with Saved"), original: 'File: Compare Active File with Saved' }, category); appendToCommandPalette(REVEAL_IN_OS_COMMAND_ID, { value: REVEAL_IN_OS_LABEL, original: isWindows ? 'File: Reveal in Explorer' : isMacintosh ? 'File: Reveal in Finder' : 'File: Open Containing Folder' }, category); -appendToCommandPalette(SAVE_FILE_AS_COMMAND_ID, { value: SAVE_FILE_AS_LABEL, original: 'File: Save As...' }, category, FileDialogContext.isEqualTo('local')); +appendToCommandPalette(SAVE_FILE_AS_COMMAND_ID, { value: SAVE_FILE_AS_LABEL, original: 'File: Save As...' }, category); appendToCommandPalette(CLOSE_EDITOR_COMMAND_ID, { value: nls.localize('closeEditor', "Close Editor"), original: 'View: Close Editor' }, nls.localize('view', "View")); appendToCommandPalette(NEW_FILE_COMMAND_ID, { value: NEW_FILE_LABEL, original: 'File: New File' }, category); appendToCommandPalette(NEW_FOLDER_COMMAND_ID, { value: NEW_FOLDER_LABEL, original: 'File: New Folder' }, category); @@ -450,7 +449,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { id: ADD_ROOT_FOLDER_COMMAND_ID, title: ADD_ROOT_FOLDER_LABEL }, - when: ContextKeyExpr.and(ExplorerRootContext, FileDialogContext.isEqualTo('local')) + when: ContextKeyExpr.and(ExplorerRootContext) }); MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { @@ -541,8 +540,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { id: SAVE_FILE_AS_COMMAND_ID, title: nls.localize({ key: 'miSaveAs', comment: ['&& denotes a mnemonic'] }, "Save &&As...") }, - order: 2, - when: FileDialogContext.isEqualTo('local') + order: 2 }); MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { diff --git a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts index c2c1ee52f58b9197da5fa6de7478ed69e1b0b242..0921ac2dc62affb9432206d97755d7f5d5db8e2d 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts @@ -10,10 +10,17 @@ import product from 'vs/platform/node/product'; import { TPromise } from 'vs/base/common/winjs.base'; import Severity from 'vs/base/common/severity'; import { isLinux, isWindows } from 'vs/base/common/platform'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IWindowService, INativeOpenDialogOptions } 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 { IDialogService, IConfirmation, IConfirmationResult, IDialogOptions, IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { ILogService } from 'vs/platform/log/common/log'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { URI } from 'vs/base/common/uri'; +import { Schemas } from 'vs/base/common/network'; +import * as resources from 'vs/base/common/resources'; +import { isParent } from 'vs/platform/files/common/files'; interface IMassagedMessageBoxOptions { @@ -146,4 +153,147 @@ export class DialogService implements IDialogService { return { options, buttonIndexMap }; } +} + +export class FileDialogService implements IFileDialogService { + + _serviceBrand: any; + + constructor( + @IWindowService private windowService: IWindowService, + @IWorkspaceContextService private contextService: IWorkspaceContextService, + @IHistoryService private historyService: IHistoryService, + @IEnvironmentService private environmentService: IEnvironmentService + ) { } + + public defaultFilePath(schemeFilter: string): URI { + let candidate: URI; + + // Check for last active file first... + candidate = this.historyService.getLastActiveFile(schemeFilter); + + // ...then for last active file root + if (!candidate) { + candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); + } + + return candidate ? resources.dirname(candidate) : void 0; + } + + public defaultFolderPath(schemeFilter: string): URI { + let candidate: URI; + + // Check for last active file root first... + candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); + + // ...then for last active file + if (!candidate) { + candidate = this.historyService.getLastActiveFile(schemeFilter); + } + + return candidate ? resources.dirname(candidate) : void 0; + } + + public defaultWorkspacePath(schemeFilter: string): URI { + + // Check for current workspace config file first... + if (schemeFilter === Schemas.file && this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE && !isUntitledWorkspace(this.contextService.getWorkspace().configuration.fsPath, this.environmentService)) { + return resources.dirname(this.contextService.getWorkspace().configuration); + } + + // ...then fallback to default folder path + return this.defaultFolderPath(schemeFilter); + } + + private toNativeOpenDialogOptions(options: IPickAndOpenOptions): INativeOpenDialogOptions { + return { + forceNewWindow: options.forceNewWindow, + telemetryExtraData: options.telemetryExtraData, + dialogOptions: { + defaultPath: options.defaultUri && options.defaultUri.fsPath + } + }; + } + + public pickFileFolderAndOpen(options: IPickAndOpenOptions): TPromise { + let defaultUri = options.defaultUri; + if (!defaultUri) { + options.defaultUri = this.defaultFilePath(Schemas.file); + } + return this.windowService.pickFileFolderAndOpen(this.toNativeOpenDialogOptions(options)); + + } + + public pickFileAndOpen(options: IPickAndOpenOptions): TPromise { + let defaultUri = options.defaultUri; + if (!defaultUri) { + options.defaultUri = this.defaultFilePath(Schemas.file); + } + return this.windowService.pickFileAndOpen(this.toNativeOpenDialogOptions(options)); + } + + public pickFolderAndOpen(options: IPickAndOpenOptions): TPromise { + let defaultUri = options.defaultUri; + if (!defaultUri) { + options.defaultUri = this.defaultFolderPath(Schemas.file); + } + return this.windowService.pickFolderAndOpen(this.toNativeOpenDialogOptions(options)); + } + + public pickWorkspaceAndOpen(options: IPickAndOpenOptions): TPromise { + let defaultUri = options.defaultUri; + if (!defaultUri) { + options.defaultUri = this.defaultWorkspacePath(Schemas.file); + } + return this.windowService.pickWorkspaceAndOpen(this.toNativeOpenDialogOptions(options)); + } + + private toNativeSaveDialogOptions(options: ISaveDialogOptions): Electron.SaveDialogOptions { + return { + defaultPath: options.defaultUri && options.defaultUri.fsPath, + buttonLabel: options.buttonLabel, + filters: options.filters, + title: options.title + }; + } + + public showSaveDialog(options: ISaveDialogOptions): TPromise { + return this.windowService.showSaveDialog(this.toNativeSaveDialogOptions(options)).then(result => { + if (result) { + return URI.file(result); + } + return void 0; + }); + } + + public showOpenDialog(options: IOpenDialogOptions): TPromise { + const defaultUri = options.defaultUri; + const filters = []; + if (options.filters) { + for (let name in options.filters) { + filters.push({ name, extensions: options.filters[name] }); + } + } + const properties = []; + if (options.canSelectFiles) { + properties.push('openFile'); + } + if (options.canSelectFolders) { + properties.push('openDirectory'); + } + if (options.canSelectMany) { + properties.push('multiSelections'); + } + return this.windowService.showOpenDialog({ + title: options.title, + defaultPath: defaultUri && defaultUri.fsPath, + buttonLabel: options.openLabel, + filters, + properties + }).then(result => result ? result.map(URI.file) : void 0); + } +} + +function isUntitledWorkspace(path: string, environmentService: IEnvironmentService): boolean { + return isParent(path, environmentService.workspacesHome, !isLinux /* ignore case */); } \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts index 3ecf18afcd5cd37f41cce957e8999737e3479252..73f34a6560d673aae7d66b3ebc782155c9b765b2 100644 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ b/src/vs/workbench/services/textfile/common/textFileService.ts @@ -12,7 +12,7 @@ import * as errors from 'vs/base/common/errors'; import * as objects from 'vs/base/common/objects'; import { Event, Emitter } from 'vs/base/common/event'; import * as platform from 'vs/base/common/platform'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext, IWillMoveEvent } from 'vs/workbench/services/textfile/common/textfiles'; import { ConfirmResult, IRevertOptions } from 'vs/workbench/common/editor'; @@ -33,7 +33,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; import { IModelService } from 'vs/editor/common/services/modelService'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { isEqualOrParent, isEqual } from 'vs/base/common/resources'; +import { isEqualOrParent, isEqual, joinPath } from 'vs/base/common/resources'; export interface IBackupResult { didBackup: boolean; @@ -76,6 +76,7 @@ export abstract class TextFileService extends Disposable implements ITextFileSer protected environmentService: IEnvironmentService, private backupFileService: IBackupFileService, private windowsService: IWindowsService, + protected windowService: IWindowService, private historyService: IHistoryService, contextKeyService: IContextKeyService, private modelService: IModelService @@ -99,7 +100,7 @@ export abstract class TextFileService extends Disposable implements ITextFileSer abstract resolveTextContent(resource: URI, options?: IResolveContentOptions): TPromise; - abstract promptForPath(resource: URI, defaultPath: string): TPromise; + abstract promptForPath(resource: URI, defaultPath: URI): TPromise; abstract confirmSave(resources?: URI[]): TPromise; @@ -451,7 +452,7 @@ export abstract class TextFileService extends Disposable implements ITextFileSer }); } - targetUri = URI.file(targetPath); + targetUri = targetPath; } targetsForUntitled.push(targetUri); @@ -536,18 +537,12 @@ export abstract class TextFileService extends Disposable implements ITextFileSer if (target) { targetPromise = TPromise.wrap(target); } else { - let dialogPath = resource.fsPath; + let dialogPath = resource; if (resource.scheme === Schemas.untitled) { dialogPath = this.suggestFileName(resource); } - targetPromise = this.promptForPath(resource, dialogPath).then(pathRaw => { - if (pathRaw) { - return URI.file(pathRaw); - } - - return void 0; - }); + targetPromise = this.promptForPath(resource, dialogPath); } return targetPromise.then(target => { @@ -630,20 +625,22 @@ export abstract class TextFileService extends Disposable implements ITextFileSer }); } - private suggestFileName(untitledResource: URI): string { + private suggestFileName(untitledResource: URI): URI { const untitledFileName = this.untitledEditorService.suggestFileName(untitledResource); - const lastActiveFile = this.historyService.getLastActiveFile(Schemas.file); + const schemeFilter = Schemas.file; + + const lastActiveFile = this.historyService.getLastActiveFile(schemeFilter); if (lastActiveFile) { - return URI.file(paths.join(paths.dirname(lastActiveFile.fsPath), untitledFileName)).fsPath; + return joinPath(lastActiveFile, untitledFileName); } - const lastActiveFolder = this.historyService.getLastActiveWorkspaceRoot(Schemas.file); + const lastActiveFolder = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); if (lastActiveFolder) { - return URI.file(paths.join(lastActiveFolder.fsPath, untitledFileName)).fsPath; + return joinPath(lastActiveFolder, untitledFileName); } - return untitledFileName; + return URI.file(untitledFileName); } revert(resource: URI, options?: IRevertOptions): TPromise { diff --git a/src/vs/workbench/services/textfile/electron-browser/textFileService.ts b/src/vs/workbench/services/textfile/electron-browser/textFileService.ts index 90d988f4933c942b04622bca4d2851de4c8a7997..fde5969a46ee7158859f65a547608d2fe85191b1 100644 --- a/src/vs/workbench/services/textfile/electron-browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/textFileService.ts @@ -29,7 +29,7 @@ import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IModelService } from 'vs/editor/common/services/modelService'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { getConfirmMessage, IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { getConfirmMessage, IDialogService, ISaveDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class TextFileService extends AbstractTextFileService { @@ -43,7 +43,7 @@ export class TextFileService extends AbstractTextFileService { @IConfigurationService configurationService: IConfigurationService, @IModeService private modeService: IModeService, @IModelService modelService: IModelService, - @IWindowService private windowService: IWindowService, + @IWindowService windowService: IWindowService, @IEnvironmentService environmentService: IEnvironmentService, @INotificationService notificationService: INotificationService, @IBackupFileService backupFileService: IBackupFileService, @@ -51,9 +51,10 @@ export class TextFileService extends AbstractTextFileService { @IHistoryService historyService: IHistoryService, @IContextKeyService contextKeyService: IContextKeyService, @IDialogService private dialogService: IDialogService, + @IFileDialogService private fileDialogService: IFileDialogService, @IEditorService private editorService: IEditorService ) { - super(lifecycleService, contextService, configurationService, fileService, untitledEditorService, instantiationService, notificationService, environmentService, backupFileService, windowsService, historyService, contextKeyService, modelService); + super(lifecycleService, contextService, configurationService, fileService, untitledEditorService, instantiationService, notificationService, environmentService, backupFileService, windowsService, windowService, historyService, contextKeyService, modelService); } resolveTextContent(resource: URI, options?: IResolveContentOptions): TPromise { @@ -104,16 +105,19 @@ export class TextFileService extends AbstractTextFileService { }); } - promptForPath(resource: URI, defaultPath: string): TPromise { + promptForPath(resource: URI, defaultUri: URI): TPromise { // Help user to find a name for the file by opening it first return this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true, } }).then(() => { - return this.windowService.showSaveDialog(this.getSaveDialogOptions(defaultPath)); + return this.fileDialogService.showSaveDialog(this.getSaveDialogOptions(defaultUri)); }); } - private getSaveDialogOptions(defaultPath: string): Electron.SaveDialogOptions { - const options: Electron.SaveDialogOptions = { defaultPath }; + private getSaveDialogOptions(defaultUri: URI): ISaveDialogOptions { + const options: ISaveDialogOptions = { + defaultUri, + title: nls.localize('saveAsTitle', "Save As") + }; // Filters are only enabled on Windows where they work properly if (!isWindows) { @@ -123,7 +127,7 @@ export class TextFileService extends AbstractTextFileService { interface IFilter { name: string; extensions: string[]; } // Build the file filter by using our known languages - const ext: string = defaultPath ? paths.extname(defaultPath) : void 0; + const ext: string = defaultUri ? paths.extname(defaultUri.path) : void 0; let matchingFilter: IFilter; const filters: IFilter[] = this.modeService.getRegisteredLanguageNames().map(languageName => { const extensions = this.modeService.getExtensions(languageName); diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index f133ab8b10cddd581d2bd94b30b66e93c22d1e03..34a85636bd81e03dd716e3eca347fc4cd3a916d1 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -254,7 +254,7 @@ suite('Files - TextFileService', () => { (accessor.textFileService.models).add(model.getResource(), model); const service = accessor.textFileService; - service.setPromptPath(model.getResource().fsPath); + service.setPromptPath(model.getResource()); return model.load().then(() => { model.textEditorModel.setValue('foo'); @@ -273,7 +273,7 @@ suite('Files - TextFileService', () => { (accessor.textFileService.models).add(model.getResource(), model); const service = accessor.textFileService; - service.setPromptPath(model.getResource().fsPath); + service.setPromptPath(model.getResource()); return model.load().then(() => { model.textEditorModel.setValue('foo'); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index e77466792d6791778071e7fa117120664f672361..8b283a579a827ba93dbeba8100e03eb415f8b801 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -59,7 +59,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService, MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { ITextBufferFactory, DefaultEndOfLine, EndOfLinePreference, IModelDecorationOptions, ITextModel } from 'vs/editor/common/model'; import { Range } from 'vs/editor/common/core/range'; -import { IConfirmation, IConfirmationResult, IDialogService, IDialogOptions } from 'vs/platform/dialogs/common/dialogs'; +import { IConfirmation, IConfirmationResult, IDialogService, IDialogOptions, IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { IExtensionService, ProfileSession, IExtensionsStatus, ExtensionPointContribution, IExtensionDescription } from '../services/extensions/common/extensions'; @@ -173,7 +173,7 @@ export class TestContextService implements IWorkspaceContextService { export class TestTextFileService extends TextFileService { public cleanupBackupsBeforeShutdownCalled: boolean; - private promptPath: string; + private promptPath: URI; private confirmResult: ConfirmResult; private resolveTextContentError: FileOperationError; @@ -187,14 +187,15 @@ export class TestTextFileService extends TextFileService { @INotificationService notificationService: INotificationService, @IBackupFileService backupFileService: IBackupFileService, @IWindowsService windowsService: IWindowsService, + @IWindowService windowService: IWindowService, @IHistoryService historyService: IHistoryService, @IContextKeyService contextKeyService: IContextKeyService, @IModelService modelService: IModelService ) { - super(lifecycleService, contextService, configurationService, fileService, untitledEditorService, instantiationService, notificationService, TestEnvironmentService, backupFileService, windowsService, historyService, contextKeyService, modelService); + super(lifecycleService, contextService, configurationService, fileService, untitledEditorService, instantiationService, notificationService, TestEnvironmentService, backupFileService, windowsService, windowService, historyService, contextKeyService, modelService); } - public setPromptPath(path: string): void { + public setPromptPath(path: URI): void { this.promptPath = path; } @@ -226,7 +227,7 @@ export class TestTextFileService extends TextFileService { }); } - public promptForPath(resource: URI, defaultPath: string): TPromise { + public promptForPath(resource: URI, defaultPath: URI): TPromise { return TPromise.wrap(this.promptPath); } @@ -276,7 +277,7 @@ export function workbenchInstantiationService(): IInstantiationService { instantiationService.stub(IHashService, new TestHashService()); instantiationService.stub(ILogService, new TestLogService()); instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService([new TestEditorGroup(0)])); - instantiationService.stub(ILabelService, new LabelService(TestEnvironmentService, workspaceContextService)); + instantiationService.stub(ILabelService, instantiationService.createInstance(LabelService)); const editorService = new TestEditorService(); instantiationService.stub(IEditorService, editorService); instantiationService.stub(ICodeEditorService, new TestCodeEditorService()); @@ -392,6 +393,39 @@ export class TestDialogService implements IDialogService { } } +export class TestFileDialogService implements IFileDialogService { + + public _serviceBrand: any; + + public defaultFilePath(schemeFilter: string): URI { + return void 0; + } + public defaultFolderPath(schemeFilter: string): URI { + return void 0; + } + public defaultWorkspacePath(schemeFilter: string): URI { + return void 0; + } + public pickFileFolderAndOpen(options: IPickAndOpenOptions): TPromise { + return TPromise.as(0); + } + public pickFileAndOpen(options: IPickAndOpenOptions): TPromise { + return TPromise.as(0); + } + public pickFolderAndOpen(options: IPickAndOpenOptions): TPromise { + return TPromise.as(0); + } + public pickWorkspaceAndOpen(options: IPickAndOpenOptions): TPromise { + return TPromise.as(0); + } + public showSaveDialog(options: ISaveDialogOptions): TPromise { + return TPromise.as(void 0); + } + public showOpenDialog(options: IOpenDialogOptions): TPromise { + return TPromise.as(void 0); + } +} + export class TestPartService implements IPartService { public _serviceBrand: any;