提交 04271d10 编写于 作者: A Alex Ross

Add alternate dialog

上级 43d68b60
......@@ -11,3 +11,6 @@ export const InputFocusedContext = new RawContextKey<boolean>(InputFocusedContex
export const IsMacContext = new RawContextKey<boolean>('isMac', isMacintosh);
export const IsLinuxContext = new RawContextKey<boolean>('isLinux', isLinux);
export const IsWindowsContext = new RawContextKey<boolean>('isWindows', isWindows);
export const SupportsWorkspacesContext = new RawContextKey<boolean>('supportsWorkspaces', true);
export const SupportsOpenFileFolderContext = new RawContextKey<boolean>('supportsOpenFileFolder', isMacintosh);
......@@ -19,7 +19,7 @@ export interface ILabelService {
* If relative is passed returns a label relative to the workspace root that the uri belongs to.
* If noPrefix is passed does not tildify the label and also does not prepand the root name for relative labels in a multi root scenario.
*/
getUriLabel(resource: URI, options?: { relative?: boolean, noPrefix?: boolean }): string;
getUriLabel(resource: URI, options?: { relative?: boolean, noPrefix?: boolean, endWithSeparator?: boolean }): string;
getWorkspaceLabel(workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWorkspace), options?: { verbose: boolean }): string;
getHostLabel(): string;
registerFormatter(formatter: ResourceLabelFormatter): IDisposable;
......
......@@ -23,6 +23,7 @@ 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 { SupportsWorkspacesContext } from 'vs/platform/contextkey/common/contextkeys';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
// Contribute Global Actions
......@@ -478,7 +479,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
id: ADD_ROOT_FOLDER_COMMAND_ID,
title: ADD_ROOT_FOLDER_LABEL
},
when: ContextKeyExpr.and(ExplorerRootContext)
when: ContextKeyExpr.and(ExplorerRootContext, SupportsWorkspacesContext)
});
MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
......
......@@ -46,6 +46,7 @@ import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree';
import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel';
import { onUnexpectedError } from 'vs/base/common/errors';
import { sequence } from 'vs/base/common/async';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
export const NEW_FILE_COMMAND_ID = 'explorer.newFile';
export const NEW_FILE_LABEL = nls.localize('newFile', "New File");
......@@ -874,8 +875,8 @@ export class ShowOpenedFileInNewWindow extends Action {
}
public run(): Promise<any> {
const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: true, filter: Schemas.file /* todo@remote */ });
if (fileResource) {
const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: true });
if (fileResource && (fileResource.scheme === Schemas.file || fileResource.scheme === REMOTE_HOST_SCHEME)) {
this.windowService.openWindow([{ uri: fileResource, typeHint: 'file' }], { forceNewWindow: true, forceOpenWorkspaceAsFile: true });
} else {
this.notificationService.info(nls.localize('openFileToShowInNewWindow', "Open a file first to open in new window"));
......
......@@ -21,7 +21,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ADD_ROOT_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands';
import { IsMacContext } from 'vs/platform/contextkey/common/contextkeys';
import { SupportsOpenFileFolderContext, SupportsWorkspacesContext, IsMacContext } from 'vs/platform/contextkey/common/contextkeys';
import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
......@@ -61,12 +61,9 @@ 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);
if (isMacintosh) {
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 }), '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(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open...', fileCategory, SupportsOpenFileFolderContext);
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open File...', fileCategory, SupportsOpenFileFolderContext.toNegated());
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, SupportsOpenFileFolderContext.toNegated());
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(CloseWorkspaceAction, CloseWorkspaceAction.ID, CloseWorkspaceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'File: Close Workspace', fileCategory);
......@@ -115,10 +112,10 @@ workbenchActionsRegistry.registerWorkbenchAction(
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleFullScreenAction, ToggleFullScreenAction.ID, ToggleFullScreenAction.LABEL, { primary: KeyCode.F11, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_F } }), 'View: Toggle Full Screen', viewCategory);
const workspacesCategory = nls.localize('workspaces', "Workspaces");
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL), 'Workspaces: Add Folder to Workspace...', workspacesCategory);
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL), 'Workspaces: Add Folder to Workspace...', workspacesCategory, SupportsWorkspacesContext);
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);
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SaveWorkspaceAsAction, SaveWorkspaceAsAction.ID, SaveWorkspaceAsAction.LABEL), 'Workspaces: Save Workspace As...', workspacesCategory);
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceAction, OpenWorkspaceAction.ID, OpenWorkspaceAction.LABEL), 'Workspaces: Open Workspace...', workspacesCategory, SupportsWorkspacesContext);
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SaveWorkspaceAsAction, SaveWorkspaceAsAction.ID, SaveWorkspaceAsAction.LABEL), 'Workspaces: Save Workspace As...', workspacesCategory, SupportsWorkspacesContext);
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(DuplicateWorkspaceInNewWindowAction, DuplicateWorkspaceInNewWindowAction.ID, DuplicateWorkspaceInNewWindowAction.LABEL), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory);
CommandsRegistry.registerCommand(OpenWorkspaceConfigFileAction.ID, serviceAccessor => {
......@@ -178,7 +175,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
title: nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File...")
},
order: 1,
when: IsMacContext.toNegated()
when: SupportsOpenFileFolderContext.toNegated()
});
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
......@@ -188,7 +185,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
title: nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...")
},
order: 2,
when: IsMacContext.toNegated()
when: SupportsOpenFileFolderContext.toNegated()
});
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
......@@ -198,7 +195,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
title: nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...")
},
order: 1,
when: IsMacContext
when: SupportsOpenFileFolderContext
});
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
......@@ -207,7 +204,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
id: OpenWorkspaceAction.ID,
title: nls.localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "Open Wor&&kspace...")
},
order: 3
order: 3,
when: SupportsWorkspacesContext
});
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
......@@ -234,7 +232,8 @@ 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
order: 1,
when: SupportsWorkspacesContext
});
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
......@@ -243,7 +242,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
id: SaveWorkspaceAsAction.ID,
title: nls.localize('miSaveWorkspaceAs', "Save Workspace As...")
},
order: 2
order: 2,
when: SupportsWorkspacesContext
});
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
......
......@@ -87,7 +87,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 } from 'vs/platform/contextkey/common/contextkeys';
import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, SupportsOpenFileFolderContext, SupportsWorkspacesContext } from 'vs/platform/contextkey/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';
......@@ -109,7 +109,7 @@ 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 { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { FileDialogService } from 'vs/workbench/services/dialogs/electron-browser/dialogService';
import { RemoteFileDialogService } from 'vs/workbench/services/dialogs/electron-browser/dialogService';
import { LogStorageAction } from 'vs/platform/storage/node/storageService';
import { Sizing, Direction, Grid, View } from 'vs/base/browser/ui/grid/grid';
import { IEditor } from 'vs/editor/common/editorCommon';
......@@ -425,7 +425,7 @@ export class Workbench extends Disposable implements IPartService {
serviceCollection.set(IHistoryService, new SyncDescriptor(HistoryService));
// File Dialogs
serviceCollection.set(IFileDialogService, new SyncDescriptor(FileDialogService));
serviceCollection.set(IFileDialogService, new SyncDescriptor(RemoteFileDialogService));
// Backup File Service
if (this.workbenchParams.configuration.backupPath) {
......@@ -627,6 +627,12 @@ export class Workbench extends Disposable implements IPartService {
IsMacContext.bindTo(this.contextKeyService);
IsLinuxContext.bindTo(this.contextKeyService);
IsWindowsContext.bindTo(this.contextKeyService);
const supportsOpenFileFolderContextKey = SupportsOpenFileFolderContext.bindTo(this.contextKeyService);
const supportsWorkspacesContextKey = SupportsWorkspacesContext.bindTo(this.contextKeyService);
if (this.windowService.getConfiguration().remoteAuthority) {
supportsOpenFileFolderContextKey.set(true);
supportsWorkspacesContextKey.set(false);
}
const sidebarVisibleContextRaw = new RawContextKey<boolean>('sidebarVisible', false);
this.sideBarVisibleContext = sidebarVisibleContextRaw.bindTo(this.contextKeyService);
......
......@@ -15,9 +15,12 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/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 { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { Schemas } from 'vs/base/common/network';
import * as resources from 'vs/base/common/resources';
import { isParent } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { RemoteFileDialog } from 'vs/workbench/services/dialogs/electron-browser/remoteFileDialog';
interface IMassagedMessageBoxOptions {
......@@ -160,7 +163,8 @@ export class FileDialogService implements IFileDialogService {
@IWindowService private readonly windowService: IWindowService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IHistoryService private readonly historyService: IHistoryService,
@IEnvironmentService private readonly environmentService: IEnvironmentService
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
) { }
defaultFilePath(schemeFilter: string): URI | undefined {
......@@ -273,6 +277,11 @@ export class FileDialogService implements IFileDialogService {
});
}
public showSaveRemoteDialog(options: ISaveDialogOptions): Promise<URI | undefined> {
const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog);
return remoteFileDialog.showSaveDialog(options);
}
showOpenDialog(options: IOpenDialogOptions): Promise<URI[] | undefined> {
const defaultUri = options.defaultUri;
if (defaultUri && defaultUri.scheme !== Schemas.file) {
......@@ -303,6 +312,32 @@ export class FileDialogService implements IFileDialogService {
return this.windowService.showOpenDialog(newOptions).then(result => result ? result.map(URI.file) : undefined);
}
public showOpenRemoteDialog(options: IOpenDialogOptions): Promise<void> {
const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog);
return remoteFileDialog.showOpenDialog(options);
}
}
export class RemoteFileDialogService extends FileDialogService {
constructor(
@IWindowService windowService: IWindowService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IHistoryService historyService: IHistoryService,
@IEnvironmentService environmentService: IEnvironmentService,
@IInstantiationService instantiationService: IInstantiationService,
) {
super(windowService, contextService, historyService, environmentService, instantiationService);
}
public showSaveDialog(options: ISaveDialogOptions): Promise<URI> {
const defaultUri = options.defaultUri;
if (defaultUri && defaultUri.scheme === REMOTE_HOST_SCHEME) {
return this.showSaveRemoteDialog(options);
}
return super.showSaveDialog(options);
}
}
function isUntitledWorkspace(path: string, environmentService: IEnvironmentService): boolean {
......
/*---------------------------------------------------------------------------------------------
* 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 * as resources from 'vs/base/common/resources';
import { RemoteFileService } from 'vs/workbench/services/files/electron-browser/remoteFileService';
import { IFileService } from 'vs/platform/files/common/files';
import { IQuickInputService, IQuickPickItem, IQuickPick } from 'vs/platform/quickinput/common/quickInput';
import { URI } from 'vs/base/common/uri';
import { isWindows } from 'vs/base/common/platform';
import { ISaveDialogOptions, IOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { ILabelService } from 'vs/platform/label/common/label';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { INotificationService } from 'vs/platform/notification/common/notification';
interface FileQuickPickItem extends IQuickPickItem {
uri: URI;
isFolder: boolean;
}
// Reference: https://en.wikipedia.org/wiki/Filename
const INVALID_FILE_CHARS = isWindows ? /[\\/:\*\?"<>\|]/g : /[\\/]/g;
const WINDOWS_FORBIDDEN_NAMES = /^(con|prn|aux|clock\$|nul|lpt[0-9]|com[0-9])$/i;
export class RemoteFileDialog {
private acceptButton = { iconPath: this.getIcons('accept.svg'), tooltip: 'Select' };
private cancelButton = { iconPath: this.getIcons('cancel.svg'), tooltip: 'Cancel' };
private currentFolder: URI;
private filePickBox: IQuickPick<FileQuickPickItem>;
private allowFileSelection: boolean;
private allowFolderSelection: boolean;
private remoteAuthority: string;
constructor(
@IFileService private readonly remoteFileService: RemoteFileService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IWindowService private readonly windowService: IWindowService,
@ILabelService private readonly labelService: ILabelService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IEditorService private readonly editorService: IEditorService,
@INotificationService private readonly notificationService: INotificationService,
) {
this.remoteAuthority = this.windowService.getConfiguration().remoteAuthority;
}
public async showOpenDialog(options: IOpenDialogOptions = {}): Promise<undefined> {
if (!this.remoteAuthority) {
this.notificationService.info(nls.localize('remoteFileDialog.notConnectedToRemote', 'Not connected to a remote.'));
return Promise.resolve(undefined);
}
const defaultUri = options.defaultUri ? options.defaultUri : URI.from({ scheme: REMOTE_HOST_SCHEME, authority: this.remoteAuthority, path: '/' });
const title = nls.localize('remoteFileDialog.openTitle', 'Open File or Folder');
return this.pickResource({ title, defaultUri, canSelectFiles: true, canSelectFolders: true }).then(async fileFolderUri => {
if (fileFolderUri) {
const stat = await this.remoteFileService.resolveFile(fileFolderUri);
if (stat.isDirectory) {
return this.windowService.openWindow([{ uri: fileFolderUri, typeHint: 'folder' }]);
} else {
return this.editorService.openEditor({ resource: fileFolderUri }).then(() => {
return Promise.resolve(undefined);
});
}
}
return Promise.resolve(undefined);
});
}
public showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined> {
if (!this.remoteAuthority) {
this.notificationService.info(nls.localize('remoteFileDialog.notConnectedToRemote', 'Not connected to a remote.'));
return Promise.resolve(undefined);
}
const defaultUri = options.defaultUri ? options.defaultUri : URI.from({ scheme: REMOTE_HOST_SCHEME, authority: this.remoteAuthority, path: '/' });
return new Promise<URI | undefined>((resolve) => {
let saveNameBox = this.quickInputService.createInputBox();
saveNameBox.title = options.title;
saveNameBox.placeholder = nls.localize('remoteFileDialog.saveTitle', 'Enter the new name of the file');
saveNameBox.value = '';
saveNameBox.totalSteps = 2;
saveNameBox.step = 1;
saveNameBox.onDidChangeValue(v => {
saveNameBox.validationMessage = this.isValidBaseName(v) ? void 0 : nls.localize('remoteFileDialog.error.invalidfilename', 'Not a valid file name');
});
saveNameBox.onDidAccept(_ => {
const name = saveNameBox.value;
if (this.isValidBaseName(name)) {
saveNameBox.hide();
this.pickResource({ defaultUri: defaultUri, canSelectFolders: true, title: nls.localize('remoteFileDialogerror.titleFolderPage', 'Folder for \'{0}\'', name) }, { step: 2, totalSteps: 2 }).then(folderUri => {
if (folderUri) {
resolve(this.remoteUriFrom(this.remotePathJoin(folderUri, name)));
} else {
resolve(undefined);
}
saveNameBox.dispose();
});
}
});
saveNameBox.show();
});
}
private remoteUriFrom(path: string): URI {
return URI.from({ scheme: REMOTE_HOST_SCHEME, authority: this.remoteAuthority, path });
}
private remotePathJoin(firstPart: URI, secondPart: string): string {
return this.labelService.getUriLabel(resources.joinPath(firstPart, secondPart));
}
private async pickResource(options: IOpenDialogOptions, multiOpts?: { step: number; totalSteps: number; }): Promise<URI | undefined> {
this.allowFolderSelection = !!options.canSelectFolders;
this.allowFileSelection = !!options.canSelectFiles;
const defaultUri = options.defaultUri;
let homedir = defaultUri && defaultUri.scheme === REMOTE_HOST_SCHEME ? defaultUri : this.workspaceContextService.getWorkspace().folders[0].uri;
return new Promise<URI | undefined>((resolve) => {
this.filePickBox = this.quickInputService.createQuickPick<FileQuickPickItem>();
if (multiOpts) {
this.filePickBox.totalSteps = multiOpts.totalSteps;
this.filePickBox.step = multiOpts.step;
}
let isResolved = false;
let isAcceptHandled = false;
this.currentFolder = homedir;
this.filePickBox.buttons = [this.acceptButton, this.cancelButton];
this.filePickBox.onDidTriggerButton(button => {
if (button === this.acceptButton) {
resolve(this.currentFolder);
isResolved = true;
}
this.filePickBox.hide();
});
this.filePickBox.title = options.title;
this.filePickBox.placeholder = this.labelService.getUriLabel(this.currentFolder, { endWithSeparator: true });
this.filePickBox.items = [];
this.filePickBox.onDidAccept(_ => {
if (isAcceptHandled || this.filePickBox.busy) {
return;
}
isAcceptHandled = true;
if (this.filePickBox.activeItems.length === 0) {
if (this.allowFolderSelection) {
resolve(this.currentFolder);
isResolved = true;
this.filePickBox.hide();
}
} else if (this.filePickBox.activeItems.length === 1) {
const item = this.filePickBox.selectedItems[0];
if (item) {
if (!item.isFolder) {
resolve(item.uri);
isResolved = true;
this.filePickBox.hide();
} else {
this.updateItems(item.uri);
}
}
}
});
this.filePickBox.onDidChangeActive(i => {
isAcceptHandled = false;
});
let to: NodeJS.Timer | undefined;
this.filePickBox.onDidChangeValue(value => {
if (to) {
clearTimeout(to);
}
if (this.endsWithSlash(value)) {
to = undefined;
this.onValueChange();
} else {
to = setTimeout(this.onValueChange, 300);
}
});
this.filePickBox.onDidHide(() => {
if (!isResolved) {
resolve(undefined);
}
this.filePickBox.dispose();
});
this.filePickBox.show();
this.updateItems(homedir);
});
}
private async onValueChange() {
if (this.filePickBox) {
let fullPath = this.remoteUriFrom(this.filePickBox.value);
let stat = await this.remoteFileService.resolveFile(fullPath);
if (!stat.isDirectory && this.allowFileSelection) {
this.updateItems(resources.dirname(fullPath));
this.filePickBox.value = resources.basename(fullPath);
} else if (stat.isDirectory) {
this.updateItems(fullPath);
}
}
}
private updateItems(newFolder: URI) {
this.currentFolder = newFolder;
this.filePickBox.placeholder = this.labelService.getUriLabel(newFolder, { endWithSeparator: true });
this.filePickBox.value = '';
this.filePickBox.busy = true;
this.createItems(this.currentFolder).then(items => {
this.filePickBox.items = items;
if (this.allowFolderSelection) {
this.filePickBox.activeItems = [];
}
this.filePickBox.busy = false;
});
}
private isValidBaseName(name: string): boolean {
if (!name || name.length === 0 || /^\s+$/.test(name)) {
return false; // require a name that is not just whitespace
}
INVALID_FILE_CHARS.lastIndex = 0; // the holy grail of software development
if (INVALID_FILE_CHARS.test(name)) {
return false; // check for certain invalid file characters
}
if (isWindows && WINDOWS_FORBIDDEN_NAMES.test(name)) {
return false; // check for certain invalid file names
}
if (name === '.' || name === '..') {
return false; // check for reserved values
}
if (isWindows && name[name.length - 1] === '.') {
return false; // Windows: file cannot end with a "."
}
if (isWindows && name.length !== name.trim().length) {
return false; // Windows: file cannot end with a whitespace
}
return true;
}
private endsWithSlash(s: string) {
return /[\/\\]$/.test(s);
}
private basenameWithTrailingSlash(fullPath: URI): string {
const child = this.labelService.getUriLabel(fullPath, { endWithSeparator: true });
const parent = this.labelService.getUriLabel(resources.dirname(fullPath), { endWithSeparator: true });
return child.substring(parent.length);
}
private createBackItem(currFolder: URI): FileQuickPickItem | null {
const parentFolder = resources.dirname(currFolder);
if (!resources.isEqual(currFolder, parentFolder)) {
return { label: '..', uri: resources.dirname(currFolder), isFolder: true };
}
return null;
}
private async createItems(currentFolder: URI): Promise<FileQuickPickItem[]> {
const result: FileQuickPickItem[] = [];
const backDir = this.createBackItem(currentFolder);
if (backDir) {
result.push(backDir);
}
try {
const fileNames = await this.remoteFileService.readFolder(currentFolder);
const items = await Promise.all(fileNames.map(fileName => this.createItem(fileName, currentFolder)));
for (let item of items) {
if (item) {
result.push(item);
}
}
} catch (e) {
// ignore
console.log(e);
}
return result.sort((i1, i2) => {
if (i1.isFolder !== i2.isFolder) {
return i1.isFolder ? -1 : 1;
}
return i1.label.localeCompare(i2.label);
});
}
private async createItem(filename: string, parent: URI): Promise<FileQuickPickItem | null> {
let fullPath = resources.joinPath(parent, filename);
try {
const stat = await this.remoteFileService.resolveFile(fullPath);
if (stat.isDirectory) {
filename = this.basenameWithTrailingSlash(fullPath);
return { label: filename, uri: fullPath, isFolder: true };
} else if (!stat.isDirectory && this.allowFileSelection) {
return { label: filename, uri: fullPath, isFolder: false };
}
return null;
} catch (e) {
return null;
}
}
private getIcons(name: string): { light: URI, dark: URI } {
return {
light: URI.parse(require.toUrl(`vs/workbench/services/dialogs/media/light/${name}`)),
dark: URI.parse(require.toUrl(`vs/workbench/services/dialogs/media/dark/${name}`))
};
}
}
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-2 -2 16 16" enable-background="new -2 -2 16 16"><polygon fill="#C5C5C5" points="9,0 4.5,9 3,6 0,6 3,12 6,12 12,0"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#e8e8e8" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><polygon points="5.382,13 2.382,7 6.618,7 7,7.764 9.382,3 13.618,3 8.618,13" fill="#F6F6F6"/><path d="M12 4l-4 8h-2l-2-4h2l1 2 3-6h2z" fill="#424242"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#424242" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>
\ No newline at end of file
......@@ -14,7 +14,7 @@ import { IWorkspaceContextService, IWorkspace } from 'vs/platform/workspace/comm
import { isEqual, basenameOrAuthority, isEqualOrParent, basename, joinPath, dirname } from 'vs/base/common/resources';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { tildify, getPathLabel } from 'vs/base/common/labels';
import { ltrim } from 'vs/base/common/strings';
import { ltrim, endsWith } from 'vs/base/common/strings';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION, toWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { Schemas } from 'vs/base/common/network';
import { IWindowService } from 'vs/platform/windows/common/windows';
......@@ -130,12 +130,14 @@ export class LabelService implements ILabelService {
return bestResult ? bestResult.formatting : undefined;
}
getUriLabel(resource: URI, options: { relative?: boolean, noPrefix?: boolean } = {}): string {
getUriLabel(resource: URI, options: { relative?: boolean, noPrefix?: boolean, endWithSeparator?: boolean } = {}): string {
const formatting = this.findFormatting(resource);
if (!formatting) {
return getPathLabel(resource.path, this.environmentService, options.relative ? this.contextService : undefined);
}
let label: string;
if (options.relative) {
const baseResource = this.contextService && this.contextService.getWorkspaceFolder(resource);
if (baseResource) {
......@@ -153,11 +155,13 @@ export class LabelService implements ILabelService {
relativeLabel = relativeLabel ? (rootName + '' + relativeLabel) : rootName; // always show root basename if there are multiple
}
return relativeLabel;
label = relativeLabel;
}
} else {
label = this.formatUri(resource, formatting, options.noPrefix);
}
return this.formatUri(resource, formatting, options.noPrefix);
return options.endWithSeparator ? this.appendSeparatorIfMissing(label, formatting) : label;
}
getWorkspaceLabel(workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWorkspace), options?: { verbose: boolean }): string {
......@@ -247,4 +251,12 @@ export class LabelService implements ILabelService {
return label.replace(sepRegexp, formatting.separator);
}
private appendSeparatorIfMissing(label: string, formatting: ResourceLabelFormatting): string {
let appendedLabel = label;
if (!endsWith(label, formatting.separator)) {
appendedLabel += formatting.separator;
}
return appendedLabel;
}
}
......@@ -32,6 +32,7 @@ import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/text
import { IModelService } from 'vs/editor/common/services/modelService';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { isEqualOrParent, isEqual, joinPath, dirname } from 'vs/base/common/resources';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
export interface IBackupResult {
didBackup: boolean;
......@@ -434,7 +435,8 @@ export abstract class TextFileService extends Disposable implements ITextFileSer
// Untitled with associated file path don't need to prompt
if (this.untitledEditorService.hasAssociatedFilePath(untitled)) {
targetUri = untitled.with({ scheme: Schemas.file });
const authority = this.windowService.getConfiguration().remoteAuthority;
targetUri = authority ? untitled.with({ scheme: REMOTE_HOST_SCHEME, authority }) : untitled.with({ scheme: Schemas.file });
}
// Otherwise ask user
......@@ -616,7 +618,8 @@ export abstract class TextFileService extends Disposable implements ITextFileSer
private suggestFileName(untitledResource: URI): URI {
const untitledFileName = this.untitledEditorService.suggestFileName(untitledResource);
const schemeFilter = Schemas.file;
const remoteAuthority = this.windowService.getConfiguration().remoteAuthority;
const schemeFilter = remoteAuthority ? REMOTE_HOST_SCHEME : Schemas.file;
const lastActiveFile = this.historyService.getLastActiveFile(schemeFilter);
if (lastActiveFile) {
......@@ -629,7 +632,7 @@ export abstract class TextFileService extends Disposable implements ITextFileSer
return joinPath(lastActiveFolder, untitledFileName);
}
return URI.file(untitledFileName);
return schemeFilter === Schemas.file ? URI.file(untitledFileName) : URI.from({ scheme: schemeFilter, authority: remoteAuthority, path: untitledFileName });
}
revert(resource: URI, options?: IRevertOptions): Promise<boolean> {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册