Vscode merge (#4582)

* Merge from vscode 37cb23d3dd4f9433d56d4ba5ea3203580719a0bd

* fix issues with merges

* bump node version in azpipe

* replace license headers

* remove duplicate launch task

* fix build errors

* fix build errors

* fix tslint issues

* working through package and linux build issues

* more work

* wip

* fix packaged builds

* working through linux build errors

* wip

* wip

* wip

* fix mac and linux file limits

* iterate linux pipeline

* disable editor typing

* revert series to parallel

* remove optimize vscode from linux

* fix linting issues

* revert testing change

* add work round for new node

* readd packaging for extensions

* fix issue with angular not resolving decorator dependencies
This commit is contained in:
Anthony Dresser
2019-03-19 17:44:35 -07:00
committed by GitHub
parent 833d197412
commit 87765e8673
1879 changed files with 54505 additions and 38058 deletions

View File

@@ -0,0 +1,258 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { IWindowService, INativeOpenDialogOptions, OpenDialogOptions, IURIToOpen, FileFilter } 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 { 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 { IInstantiationService } 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';
export class FileDialogService implements IFileDialogService {
_serviceBrand: any;
constructor(
@IWindowService private readonly windowService: IWindowService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IHistoryService private readonly historyService: IHistoryService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IConfigurationService private readonly configurationService: IConfigurationService
) { }
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 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
}
};
}
private shouldUseSimplified(schema: string): boolean {
return (schema !== Schemas.file) || (this.configurationService.getValue('workbench.dialogs.useSimplified') === 'true');
}
private ensureFileSchema(schema: string): string[] {
return schema !== Schemas.file ? [schema, Schemas.file] : [schema];
}
pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise<any> {
const schema = this.getFileSystemSchema(options);
if (!options.defaultUri) {
options.defaultUri = this.defaultFilePath(schema);
}
if (this.shouldUseSimplified(schema)) {
const title = nls.localize('openFileOrFolder.title', 'Open File Or Folder');
const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well
return this.pickRemoteResourceAndOpen({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }, !!options.forceNewWindow, true);
}
return this.windowService.pickFileFolderAndOpen(this.toNativeOpenDialogOptions(options));
}
pickFileAndOpen(options: IPickAndOpenOptions): Promise<any> {
const schema = this.getFileSystemSchema(options);
if (!options.defaultUri) {
options.defaultUri = this.defaultFilePath(schema);
}
if (this.shouldUseSimplified(schema)) {
const title = nls.localize('openFile.title', 'Open File');
const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well
return this.pickRemoteResourceAndOpen({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }, !!options.forceNewWindow, true);
}
return this.windowService.pickFileAndOpen(this.toNativeOpenDialogOptions(options));
}
pickFolderAndOpen(options: IPickAndOpenOptions): Promise<any> {
const schema = this.getFileSystemSchema(options);
if (!options.defaultUri) {
options.defaultUri = this.defaultFolderPath(schema);
}
if (this.shouldUseSimplified(schema)) {
const title = nls.localize('openFolder.title', 'Open Folder');
const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well
return this.pickRemoteResourceAndOpen({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }, !!options.forceNewWindow, false);
}
return this.windowService.pickFolderAndOpen(this.toNativeOpenDialogOptions(options));
}
pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise<void> {
const schema = this.getFileSystemSchema(options);
if (!options.defaultUri) {
options.defaultUri = this.defaultWorkspacePath(schema);
}
if (this.shouldUseSimplified(schema)) {
const title = nls.localize('openWorkspace.title', 'Open Workspace');
const filters: FileFilter[] = [{ name: nls.localize('filterName.workspace', 'Workspace'), extensions: [WORKSPACE_EXTENSION] }];
const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well
return this.pickRemoteResourceAndOpen({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems }, !!options.forceNewWindow, false);
}
return this.windowService.pickWorkspaceAndOpen(this.toNativeOpenDialogOptions(options));
}
private toNativeSaveDialogOptions(options: ISaveDialogOptions): Electron.SaveDialogOptions {
return {
defaultPath: options.defaultUri && options.defaultUri.fsPath,
buttonLabel: options.saveLabel,
filters: options.filters,
title: options.title
};
}
showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined> {
const schema = this.getFileSystemSchema(options);
if (this.shouldUseSimplified(schema)) {
if (!options.availableFileSystems) {
options.availableFileSystems = [schema]; // by default only allow saving in the own file system
}
return this.saveRemoteResource(options);
}
return this.windowService.showSaveDialog(this.toNativeSaveDialogOptions(options)).then(result => {
if (result) {
return URI.file(result);
}
return undefined;
});
}
showOpenDialog(options: IOpenDialogOptions): Promise<URI[] | undefined> {
const schema = this.getFileSystemSchema(options);
if (schema !== Schemas.file) {
if (!options.availableFileSystems) {
options.availableFileSystems = [schema]; // by default only allow loading in the own file system
}
return this.pickRemoteResource(options).then(urisToOpen => {
return urisToOpen && urisToOpen.map(uto => uto.uri);
});
}
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');
}
return this.windowService.showOpenDialog(newOptions).then(result => result ? result.map(URI.file) : undefined);
}
private pickRemoteResourceAndOpen(options: IOpenDialogOptions, forceNewWindow: boolean, forceOpenWorkspaceAsFile: boolean) {
return this.pickRemoteResource(options).then(urisToOpen => {
if (urisToOpen) {
return this.windowService.openWindow(urisToOpen, { forceNewWindow, forceOpenWorkspaceAsFile });
}
return undefined;
});
}
private pickRemoteResource(options: IOpenDialogOptions): Promise<IURIToOpen[] | undefined> {
const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog);
return remoteFileDialog.showOpenDialog(options);
}
private saveRemoteResource(options: ISaveDialogOptions): Promise<URI | undefined> {
const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog);
return remoteFileDialog.showSaveDialog(options);
}
private getSchemeFilterForWindow() {
return !this.windowService.getConfiguration().remoteAuthority ? Schemas.file : REMOTE_HOST_SCHEME;
}
private getFileSystemSchema(options: { availableFileSystems?: string[], defaultUri?: URI }): string {
return options.availableFileSystems && options.availableFileSystems[0] || options.defaultUri && options.defaultUri.scheme || this.getSchemeFilterForWindow();
}
}
function isUntitledWorkspace(path: URI, environmentService: IEnvironmentService): boolean {
return resources.isEqualOrParent(path, environmentService.untitledWorkspacesHome);
}
registerSingleton(IFileDialogService, FileDialogService, true);

View File

@@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 194 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{opacity:0;fill:#F6F6F6;} .icon-vs-fg{fill:#F0EFF1;} .icon-folder{fill:#656565;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 2.5v10c0 .827-.673 1.5-1.5 1.5h-11.996c-.827 0-1.5-.673-1.5-1.5v-8c0-.827.673-1.5 1.5-1.5h2.886l1-2h8.11c.827 0 1.5.673 1.5 1.5z" id="outline"/><path class="icon-folder" d="M14.5 2h-7.492l-1 2h-3.504c-.277 0-.5.224-.5.5v8c0 .276.223.5.5.5h11.996c.275 0 .5-.224.5-.5v-10c0-.276-.225-.5-.5-.5zm-.496 2h-6.496l.5-1h5.996v1z" id="iconBg"/><path class="icon-vs-fg" d="M14 3v1h-6.5l.5-1h6z" id="iconFg"/></svg>

After

Width:  |  Height:  |  Size: 750 B

View File

@@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 221 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{opacity:0;fill:#F6F6F6;} .icon-vs-fg{opacity:0;fill:#F0EFF1;} .icon-folder{fill:#C5C5C5;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 2.5v10c0 .827-.673 1.5-1.5 1.5h-11.996c-.827 0-1.5-.673-1.5-1.5v-8c0-.827.673-1.5 1.5-1.5h2.886l1-2h8.11c.827 0 1.5.673 1.5 1.5z" id="outline"/><path class="icon-folder" d="M14.5 2h-7.492l-1 2h-3.504c-.277 0-.5.224-.5.5v8c0 .276.223.5.5.5h11.996c.275 0 .5-.224.5-.5v-10c0-.276-.225-.5-.5-.5zm-.496 2h-6.496l.5-1h5.996v1z" id="iconBg"/><path class="icon-vs-fg" d="M14 3v1h-6.5l.5-1h6z" id="iconFg"/></svg>

After

Width:  |  Height:  |  Size: 760 B

View File

@@ -0,0 +1,564 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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 * as objects from 'vs/base/common/objects';
import { IFileService, IFileStat, FileKind } from 'vs/platform/files/common/files';
import { IQuickInputService, IQuickPickItem, IQuickPick, IQuickInputButton } from 'vs/platform/quickinput/common/quickInput';
import { URI } from 'vs/base/common/uri';
import { isWindows } from 'vs/base/common/platform';
import { ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IWindowService, IURIToOpen, FileFilter } from 'vs/platform/windows/common/windows';
import { ILabelService } from 'vs/platform/label/common/label';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { Schemas } from 'vs/base/common/network';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IRemoteEnvironmentService } from 'vs/workbench/services/remote/common/remoteEnvironmentService';
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: IQuickInputButton;
private fallbackListItem: FileQuickPickItem | undefined;
private options: IOpenDialogOptions;
private currentFolder: URI;
private filePickBox: IQuickPick<FileQuickPickItem>;
private filters: FileFilter[] | undefined;
private hidden: boolean;
private allowFileSelection: boolean;
private allowFolderSelection: boolean;
private remoteAuthority: string | undefined;
private requiresTrailing: boolean;
private userValue: string;
private scheme: string = REMOTE_HOST_SCHEME;
private shouldOverwriteFile: boolean = false;
constructor(
@IFileService private readonly fileService: IFileService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IWindowService private readonly windowService: IWindowService,
@ILabelService private readonly labelService: ILabelService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@INotificationService private readonly notificationService: INotificationService,
@IFileDialogService private readonly fileDialogService: IFileDialogService,
@IModelService private readonly modelService: IModelService,
@IModeService private readonly modeService: IModeService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IRemoteEnvironmentService private readonly remoteEnvironmentService: IRemoteEnvironmentService,
) {
this.remoteAuthority = this.windowService.getConfiguration().remoteAuthority;
}
public async showOpenDialog(options: IOpenDialogOptions = {}): Promise<IURIToOpen[] | undefined> {
this.scheme = this.getScheme(options.defaultUri, options.availableFileSystems);
const newOptions = await this.getOptions(options);
if (!newOptions) {
return Promise.resolve(undefined);
}
this.options = newOptions;
const openFileString = nls.localize('remoteFileDialog.localFileFallback', '(Open Local File)');
const openFolderString = nls.localize('remoteFileDialog.localFolderFallback', '(Open Local Folder)');
const openFileFolderString = nls.localize('remoteFileDialog.localFileFolderFallback', '(Open Local File or Folder)');
let fallbackLabel = options.canSelectFiles ? (options.canSelectFolders ? openFileFolderString : openFileString) : openFolderString;
this.fallbackListItem = this.getFallbackFileSystem(fallbackLabel);
return this.pickResource().then(async fileFolderUri => {
if (fileFolderUri) {
const stat = await this.fileService.resolveFile(fileFolderUri);
return <IURIToOpen[]>[{ uri: fileFolderUri, typeHint: stat.isDirectory ? 'folder' : 'file' }];
}
return Promise.resolve(undefined);
});
}
public async showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined> {
this.scheme = this.getScheme(options.defaultUri, options.availableFileSystems);
this.requiresTrailing = true;
const newOptions = await this.getOptions(options);
if (!newOptions) {
return Promise.resolve(undefined);
}
this.options = newOptions;
this.options.canSelectFolders = true;
this.options.canSelectFiles = true;
this.fallbackListItem = this.getFallbackFileSystem(nls.localize('remoteFileDialog.localSaveFallback', '(Save Local File)'));
return new Promise<URI | undefined>((resolve) => {
this.pickResource(true).then(folderUri => {
resolve(folderUri);
});
});
}
private async getOptions(options: ISaveDialogOptions | IOpenDialogOptions): Promise<IOpenDialogOptions | undefined> {
let defaultUri = options.defaultUri;
if (!defaultUri) {
const env = await this.remoteEnvironmentService.remoteEnvironment;
if (env) {
defaultUri = env.userHome;
} else {
defaultUri = URI.from({ scheme: this.scheme, path: this.environmentService.userHome });
}
}
if ((this.scheme !== Schemas.file) && !this.fileService.canHandleResource(defaultUri)) {
this.notificationService.info(nls.localize('remoteFileDialog.notConnectedToRemote', 'File system provider for {0} is not available.', defaultUri.toString()));
return undefined;
}
const newOptions: IOpenDialogOptions = objects.deepClone(options);
newOptions.defaultUri = defaultUri;
return newOptions;
}
private remoteUriFrom(path: string): URI {
path = path.replace(/\\/g, '/');
return URI.from({ scheme: this.scheme, authority: this.remoteAuthority, path });
}
private getScheme(defaultUri: URI | undefined, available: string[] | undefined): string {
return defaultUri ? defaultUri.scheme : (available ? available[0] : Schemas.file);
}
private getFallbackFileSystem(label: string): FileQuickPickItem | undefined {
if (this.options && this.options.availableFileSystems && (this.options.availableFileSystems.length > 1)) {
return { label: label, uri: URI.from({ scheme: this.options.availableFileSystems[1] }), isFolder: true };
}
return undefined;
}
private async pickResource(isSave: boolean = false): Promise<URI | undefined> {
this.allowFolderSelection = !!this.options.canSelectFolders;
this.allowFileSelection = !!this.options.canSelectFiles;
this.hidden = false;
let homedir: URI = this.options.defaultUri ? this.options.defaultUri : this.workspaceContextService.getWorkspace().folders[0].uri;
let trailing: string | undefined;
let stat: IFileStat | undefined;
let ext: string = resources.extname(homedir);
if (this.options.defaultUri) {
try {
stat = await this.fileService.resolveFile(this.options.defaultUri);
} catch (e) {
// The file or folder doesn't exist
}
if (!stat || !stat.isDirectory) {
homedir = resources.dirname(this.options.defaultUri);
trailing = resources.basename(this.options.defaultUri);
}
// append extension
if (isSave && !ext && this.options.filters) {
for (let i = 0; i < this.options.filters.length; i++) {
if (this.options.filters[i].extensions[0] !== '*') {
ext = '.' + this.options.filters[i].extensions[0];
trailing = trailing ? trailing + ext : ext;
break;
}
}
}
}
this.acceptButton = { iconPath: this.getDialogIcons('accept'), tooltip: this.options.title };
return new Promise<URI | undefined>((resolve) => {
this.filePickBox = this.quickInputService.createQuickPick<FileQuickPickItem>();
this.filePickBox.matchOnLabel = false;
this.filePickBox.autoFocusOnList = false;
let isResolving = false;
let isAcceptHandled = false;
this.currentFolder = homedir;
this.filePickBox.buttons = [this.acceptButton];
this.filePickBox.onDidTriggerButton(_ => {
// accept button
const resolveValue = this.remoteUriFrom(this.filePickBox.value);
this.validate(resolveValue).then(validated => {
if (validated) {
isResolving = true;
this.filePickBox.hide();
resolve(resolveValue);
}
});
});
this.filePickBox.title = this.options.title;
this.filePickBox.value = this.pathFromUri(this.currentFolder);
this.filePickBox.items = [];
this.filePickBox.onDidAccept(_ => {
if (isAcceptHandled || this.filePickBox.busy) {
return;
}
isAcceptHandled = true;
isResolving = true;
this.onDidAccept().then(resolveValue => {
if (resolveValue) {
this.filePickBox.hide();
resolve(resolveValue);
} else if (this.hidden) {
resolve(undefined);
} else {
isResolving = false;
isAcceptHandled = false;
}
});
});
this.filePickBox.onDidChangeActive(i => {
isAcceptHandled = false;
});
this.filePickBox.onDidChangeValue(async value => {
if (value !== this.userValue) {
this.filePickBox.validationMessage = undefined;
this.shouldOverwriteFile = false;
const trimmedPickBoxValue = ((this.filePickBox.value.length > 1) && this.endsWithSlash(this.filePickBox.value)) ? this.filePickBox.value.substr(0, this.filePickBox.value.length - 1) : this.filePickBox.value;
const valueUri = this.remoteUriFrom(trimmedPickBoxValue);
if (!resources.isEqual(this.currentFolder, valueUri, true)) {
await this.tryUpdateItems(value, this.remoteUriFrom(this.filePickBox.value));
}
this.setActiveItems(value);
this.userValue = value;
} else {
this.filePickBox.activeItems = [];
}
});
this.filePickBox.onDidHide(() => {
this.hidden = true;
if (!isResolving) {
resolve(undefined);
}
this.filePickBox.dispose();
});
this.filePickBox.show();
this.updateItems(homedir, trailing);
if (trailing) {
this.filePickBox.valueSelection = [this.filePickBox.value.length - trailing.length, this.filePickBox.value.length - ext.length];
} else {
this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length];
}
this.userValue = this.filePickBox.value;
});
}
private async onDidAccept(): Promise<URI | undefined> {
// Check if Open Local has been selected
const selectedItems: ReadonlyArray<FileQuickPickItem> = this.filePickBox.selectedItems;
if (selectedItems && (selectedItems.length > 0) && (selectedItems[0] === this.fallbackListItem)) {
if (this.options.availableFileSystems && (this.options.availableFileSystems.length > 1)) {
this.options.availableFileSystems.shift();
}
if (this.requiresTrailing) {
return this.fileDialogService.showSaveDialog(this.options).then(result => {
return result;
});
} else {
return this.fileDialogService.showOpenDialog(this.options).then(result => {
return result ? result[0] : undefined;
});
}
}
let resolveValue: URI | undefined;
let navigateValue: URI | undefined;
const trimmedPickBoxValue = ((this.filePickBox.value.length > 1) && this.endsWithSlash(this.filePickBox.value)) ? this.filePickBox.value.substr(0, this.filePickBox.value.length - 1) : this.filePickBox.value;
const inputUri = this.remoteUriFrom(trimmedPickBoxValue);
const inputUriDirname = resources.dirname(inputUri);
let stat: IFileStat | undefined;
let statDirname: IFileStat | undefined;
try {
statDirname = await this.fileService.resolveFile(inputUriDirname);
stat = await this.fileService.resolveFile(inputUri);
} catch (e) {
// do nothing
}
// Find resolve value
if (this.filePickBox.activeItems.length === 0) {
if (!this.requiresTrailing && resources.isEqual(this.currentFolder, inputUri, true)) {
resolveValue = inputUri;
} else if (statDirname && statDirname.isDirectory) {
resolveValue = inputUri;
} else if (stat && stat.isDirectory) {
navigateValue = inputUri;
}
} else if (this.filePickBox.activeItems.length === 1) {
const item = this.filePickBox.selectedItems[0];
if (item) {
if (!item.isFolder) {
resolveValue = item.uri;
} else {
navigateValue = item.uri;
}
}
}
if (resolveValue) {
if (await this.validate(resolveValue)) {
return Promise.resolve(resolveValue);
}
} else if (navigateValue) {
// Try to navigate into the folder
this.updateItems(navigateValue);
} else {
// validation error. Path does not exist.
}
return Promise.resolve(undefined);
}
private async tryUpdateItems(value: string, valueUri: URI) {
if (this.endsWithSlash(value) || (!resources.isEqual(this.currentFolder, resources.dirname(valueUri), true) && resources.isEqualOrParent(this.currentFolder, resources.dirname(valueUri), true))) {
let stat: IFileStat | undefined;
try {
stat = await this.fileService.resolveFile(valueUri);
} catch (e) {
// do nothing
}
if (stat && stat.isDirectory && (resources.basename(valueUri) !== '.')) {
this.updateItems(valueUri);
} else {
const inputUriDirname = resources.dirname(valueUri);
if (!resources.isEqual(this.currentFolder, inputUriDirname, true)) {
let statWithoutTrailing: IFileStat | undefined;
try {
statWithoutTrailing = await this.fileService.resolveFile(inputUriDirname);
} catch (e) {
// do nothing
}
if (statWithoutTrailing && statWithoutTrailing.isDirectory && (resources.basename(valueUri) !== '.')) {
this.updateItems(inputUriDirname, resources.basename(valueUri));
}
}
}
}
}
private setActiveItems(value: string) {
if (!this.userValue || (value !== this.userValue.substring(0, value.length))) {
const inputBasename = resources.basename(this.remoteUriFrom(value));
let hasMatch = false;
for (let i = 0; i < this.filePickBox.items.length; i++) {
const item = <FileQuickPickItem>this.filePickBox.items[i];
const itemBasename = (item.label === '..') ? item.label : resources.basename(item.uri);
if ((itemBasename.length >= inputBasename.length) && (itemBasename.substr(0, inputBasename.length).toLowerCase() === inputBasename.toLowerCase())) {
this.filePickBox.activeItems = [item];
this.filePickBox.value = this.filePickBox.value + itemBasename.substr(inputBasename.length);
this.filePickBox.valueSelection = [value.length, this.filePickBox.value.length];
hasMatch = true;
break;
}
}
if (!hasMatch) {
this.filePickBox.activeItems = [];
}
}
}
private async validate(uri: URI): Promise<boolean> {
let stat: IFileStat | undefined;
let statDirname: IFileStat | undefined;
try {
statDirname = await this.fileService.resolveFile(resources.dirname(uri));
stat = await this.fileService.resolveFile(uri);
} catch (e) {
// do nothing
}
if (this.requiresTrailing) { // save
if (stat && stat.isDirectory) {
// Can't do this
this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateFolder', 'The folder already exists. Please use a new file name.');
return Promise.resolve(false);
} else if (stat && !this.shouldOverwriteFile) {
// Replacing a file.
this.shouldOverwriteFile = true;
this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateExisting', '{0} already exists. Are you sure you want to overwrite it?', resources.basename(uri));
return Promise.resolve(false);
} else if (!this.isValidBaseName(resources.basename(uri))) {
// Filename not allowed
this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateBadFilename', 'Please enter a valid file name.');
return Promise.resolve(false);
} else if (!statDirname || !statDirname.isDirectory) {
// Folder to save in doesn't exist
this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateNonexistentDir', 'Please enter a path that exists.');
return Promise.resolve(false);
}
} else { // open
if (!stat) {
// File or folder doesn't exist
this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateNonexistentDir', 'Please enter a path that exists.');
return Promise.resolve(false);
} else if (stat.isDirectory && !this.allowFolderSelection) {
// Folder selected when folder selection not permitted
this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateFileOnly', 'Please select a file.');
return Promise.resolve(false);
} else if (!stat.isDirectory && !this.allowFileSelection) {
// File selected when file selection not permitted
this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateFolderOnly', 'Please select a folder.');
return Promise.resolve(false);
}
}
return Promise.resolve(true);
}
private updateItems(newFolder: URI, trailing?: string) {
this.currentFolder = newFolder;
this.filePickBox.value = trailing ? this.pathFromUri(resources.joinPath(newFolder, trailing)) : this.pathFromUri(newFolder, true);
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 pathFromUri(uri: URI, endWithSeparator: boolean = false): string {
const sep = this.labelService.getSeparator(uri.scheme, uri.authority);
let result: string;
if (sep === '/') {
result = uri.fsPath.replace(/\\/g, sep);
} else {
result = uri.fsPath.replace(/\//g, sep);
}
if (endWithSeparator && !this.endsWithSlash(result)) {
result = result + sep;
}
return result;
}
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.pathFromUri(fullPath, true);
const parent = this.pathFromUri(resources.dirname(fullPath), true);
return child.substring(parent.length);
}
private createBackItem(currFolder: URI): FileQuickPickItem | null {
const parentFolder = resources.dirname(currFolder)!;
if (!resources.isEqual(currFolder, parentFolder, true)) {
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);
try {
const fileNames = await this.fileService.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);
}
const sorted = result.sort((i1, i2) => {
if (i1.isFolder !== i2.isFolder) {
return i1.isFolder ? -1 : 1;
}
const trimmed1 = this.endsWithSlash(i1.label) ? i1.label.substr(0, i1.label.length - 1) : i1.label;
const trimmed2 = this.endsWithSlash(i2.label) ? i2.label.substr(0, i2.label.length - 1) : i2.label;
return trimmed1.localeCompare(trimmed2);
});
if (backDir) {
sorted.unshift(backDir);
}
if (this.fallbackListItem) {
sorted.unshift(this.fallbackListItem);
}
return sorted;
}
private filterFile(file: URI): boolean {
if (this.filters) {
const ext = resources.extname(file);
for (let i = 0; i < this.filters.length; i++) {
for (let j = 0; j < this.filters[i].extensions.length; j++) {
if (ext === ('.' + this.filters[i].extensions[j])) {
return true;
}
}
}
return false;
}
return true;
}
private async createItem(filename: string, parent: URI): Promise<FileQuickPickItem | undefined> {
let fullPath = resources.joinPath(parent, filename);
try {
const stat = await this.fileService.resolveFile(fullPath);
if (stat.isDirectory) {
filename = this.basenameWithTrailingSlash(fullPath);
return { label: filename, uri: fullPath, isFolder: true, iconClasses: getIconClasses(this.modelService, this.modeService, fullPath || undefined, FileKind.FOLDER) };
} else if (!stat.isDirectory && this.allowFileSelection && this.filterFile(fullPath)) {
return { label: filename, uri: fullPath, isFolder: false, iconClasses: getIconClasses(this.modelService, this.modeService, fullPath || undefined) };
}
return undefined;
} catch (e) {
return undefined;
}
}
private getDialogIcons(name: string): { light: URI, dark: URI } {
return {
dark: URI.parse(require.toUrl(`vs/workbench/services/dialogs/browser/media/dark/${name}.svg`)),
light: URI.parse(require.toUrl(`vs/workbench/services/dialogs/browser/media/light/${name}.svg`))
};
}
}