mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-11 02:32:35 -05:00
Merge from vscode 8e0f348413f4f616c23a88ae30030efa85811973 (#6381)
* Merge from vscode 8e0f348413f4f616c23a88ae30030efa85811973 * disable strict null check
This commit is contained in:
@@ -86,14 +86,16 @@ export class FileDialogService implements IFileDialogService {
|
||||
|
||||
private shouldUseSimplified(schema: string): boolean {
|
||||
const setting = this.configurationService.getValue('files.simpleDialog.enable');
|
||||
|
||||
return (schema !== Schemas.file) || (setting === true);
|
||||
}
|
||||
|
||||
private ensureFileSchema(schema: string): string[] {
|
||||
return schema !== Schemas.file ? [schema, Schemas.file] : [schema];
|
||||
// Don't allow untitled schema through.
|
||||
return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]);
|
||||
}
|
||||
|
||||
pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise<any> {
|
||||
async pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise<any> {
|
||||
const schema = this.getFileSystemSchema(options);
|
||||
|
||||
if (!options.defaultUri) {
|
||||
@@ -103,21 +105,23 @@ export class FileDialogService implements IFileDialogService {
|
||||
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.pickRemoteResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }).then(uri => {
|
||||
if (uri) {
|
||||
return (this.fileService.resolve(uri)).then(stat => {
|
||||
const toOpen: IURIToOpen = stat.isDirectory ? { folderUri: uri } : { fileUri: uri };
|
||||
return this.windowService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow });
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems });
|
||||
|
||||
if (uri) {
|
||||
const stat = await this.fileService.resolve(uri);
|
||||
|
||||
const toOpen: IURIToOpen = stat.isDirectory ? { folderUri: uri } : { fileUri: uri };
|
||||
return this.windowService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow });
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return this.windowService.pickFileFolderAndOpen(this.toNativeOpenDialogOptions(options));
|
||||
}
|
||||
|
||||
pickFileAndOpen(options: IPickAndOpenOptions): Promise<any> {
|
||||
async pickFileAndOpen(options: IPickAndOpenOptions): Promise<any> {
|
||||
const schema = this.getFileSystemSchema(options);
|
||||
|
||||
if (!options.defaultUri) {
|
||||
@@ -127,18 +131,19 @@ export class FileDialogService implements IFileDialogService {
|
||||
if (this.shouldUseSimplified(schema)) {
|
||||
const title = nls.localize('openFile.title', 'Open File');
|
||||
const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well
|
||||
return this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }).then(uri => {
|
||||
if (uri) {
|
||||
return this.windowService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow });
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems });
|
||||
if (uri) {
|
||||
return this.windowService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow });
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return this.windowService.pickFileAndOpen(this.toNativeOpenDialogOptions(options));
|
||||
}
|
||||
|
||||
pickFolderAndOpen(options: IPickAndOpenOptions): Promise<any> {
|
||||
async pickFolderAndOpen(options: IPickAndOpenOptions): Promise<any> {
|
||||
const schema = this.getFileSystemSchema(options);
|
||||
|
||||
if (!options.defaultUri) {
|
||||
@@ -148,18 +153,19 @@ export class FileDialogService implements IFileDialogService {
|
||||
if (this.shouldUseSimplified(schema)) {
|
||||
const title = nls.localize('openFolder.title', 'Open Folder');
|
||||
const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well
|
||||
return this.pickRemoteResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }).then(uri => {
|
||||
if (uri) {
|
||||
return this.windowService.openWindow([{ folderUri: uri }], { forceNewWindow: options.forceNewWindow });
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const uri = await this.pickRemoteResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems });
|
||||
if (uri) {
|
||||
return this.windowService.openWindow([{ folderUri: uri }], { forceNewWindow: options.forceNewWindow });
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return this.windowService.pickFolderAndOpen(this.toNativeOpenDialogOptions(options));
|
||||
}
|
||||
|
||||
pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise<void> {
|
||||
async pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise<void> {
|
||||
const schema = this.getFileSystemSchema(options);
|
||||
|
||||
if (!options.defaultUri) {
|
||||
@@ -170,18 +176,39 @@ export class FileDialogService implements IFileDialogService {
|
||||
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.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems }).then(uri => {
|
||||
if (uri) {
|
||||
return this.windowService.openWindow([{ workspaceUri: uri }], { forceNewWindow: options.forceNewWindow });
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems });
|
||||
if (uri) {
|
||||
return this.windowService.openWindow([{ workspaceUri: uri }], { forceNewWindow: options.forceNewWindow });
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return this.windowService.pickWorkspaceAndOpen(this.toNativeOpenDialogOptions(options));
|
||||
}
|
||||
|
||||
async pickFileToSave(options: ISaveDialogOptions): Promise<URI | undefined> {
|
||||
const schema = this.getFileSystemSchema(options);
|
||||
if (this.shouldUseSimplified(schema)) {
|
||||
if (!options.availableFileSystems) {
|
||||
options.availableFileSystems = this.ensureFileSchema(schema); // always allow file as well
|
||||
}
|
||||
|
||||
options.title = nls.localize('saveFileAs.title', 'Save As');
|
||||
return this.saveRemoteResource(options);
|
||||
}
|
||||
|
||||
const result = await this.windowService.showSaveDialog(this.toNativeSaveDialogOptions(options));
|
||||
if (result) {
|
||||
return URI.file(result);
|
||||
}
|
||||
|
||||
return undefined; // {{SQL CARBON EDIT}} strict-null-check
|
||||
}
|
||||
|
||||
private toNativeSaveDialogOptions(options: ISaveDialogOptions): Electron.SaveDialogOptions {
|
||||
options.defaultUri = options.defaultUri ? URI.file(options.defaultUri.path) : undefined;
|
||||
return {
|
||||
defaultPath: options.defaultUri && options.defaultUri.fsPath,
|
||||
buttonLabel: options.saveLabel,
|
||||
@@ -190,33 +217,34 @@ export class FileDialogService implements IFileDialogService {
|
||||
};
|
||||
}
|
||||
|
||||
showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined> {
|
||||
async 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);
|
||||
}
|
||||
const result = await this.windowService.showSaveDialog(this.toNativeSaveDialogOptions(options));
|
||||
if (result) {
|
||||
return URI.file(result);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check
|
||||
}
|
||||
|
||||
showOpenDialog(options: IOpenDialogOptions): Promise<URI[] | undefined> {
|
||||
async showOpenDialog(options: IOpenDialogOptions): Promise<URI[] | undefined> {
|
||||
const schema = this.getFileSystemSchema(options);
|
||||
if (this.shouldUseSimplified(schema)) {
|
||||
if (!options.availableFileSystems) {
|
||||
options.availableFileSystems = [schema]; // by default only allow loading in the own file system
|
||||
}
|
||||
return this.pickRemoteResource(options).then(uri => {
|
||||
return uri ? [uri] : undefined;
|
||||
});
|
||||
|
||||
const uri = await this.pickRemoteResource(options);
|
||||
|
||||
return uri ? [uri] : undefined;
|
||||
}
|
||||
|
||||
const defaultUri = options.defaultUri;
|
||||
@@ -243,27 +271,30 @@ export class FileDialogService implements IFileDialogService {
|
||||
newOptions.properties!.push('multiSelections');
|
||||
}
|
||||
|
||||
return this.windowService.showOpenDialog(newOptions).then(result => result ? result.map(URI.file) : undefined);
|
||||
const result = await this.windowService.showOpenDialog(newOptions);
|
||||
|
||||
return result ? result.map(URI.file) : undefined;
|
||||
}
|
||||
|
||||
private pickRemoteResource(options: IOpenDialogOptions): Promise<URI | 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() {
|
||||
private getSchemeFilterForWindow(): string {
|
||||
return !this.environmentService.configuration.remoteAuthority ? Schemas.file : REMOTE_HOST_SCHEME;
|
||||
}
|
||||
|
||||
private getFileSystemSchema(options: { availableFileSystems?: string[], defaultUri?: URI }): string {
|
||||
return options.availableFileSystems && options.availableFileSystems[0] || options.defaultUri && options.defaultUri.scheme || this.getSchemeFilterForWindow();
|
||||
return options.availableFileSystems && options.availableFileSystems[0] || this.getSchemeFilterForWindow();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function isUntitledWorkspace(path: URI, environmentService: IWorkbenchEnvironmentService): boolean {
|
||||
|
||||
@@ -23,11 +23,15 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { equalsIgnoreCase, format, startsWithIgnoreCase } from 'vs/base/common/strings';
|
||||
import { OpenLocalFileAction, OpenLocalFileFolderAction, OpenLocalFolderAction } from 'vs/workbench/browser/actions/workspaceActions';
|
||||
import { OpenLocalFileCommand, OpenLocalFileFolderCommand, OpenLocalFolderCommand, SaveLocalFileCommand } from 'vs/workbench/browser/actions/workspaceActions';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
||||
import { isValidBasename } from 'vs/base/common/extpath';
|
||||
import { RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { createCancelablePromise, CancelablePromise } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
interface FileQuickPickItem extends IQuickPickItem {
|
||||
uri: URI;
|
||||
@@ -60,6 +64,12 @@ export class RemoteFileDialog {
|
||||
private badPath: string | undefined;
|
||||
private remoteAgentEnvironment: IRemoteAgentEnvironment | null;
|
||||
private separator: string;
|
||||
private onBusyChangeEmitter = new Emitter<boolean>();
|
||||
private updatingPromise: CancelablePromise<void> | undefined;
|
||||
|
||||
protected disposables: IDisposable[] = [
|
||||
this.onBusyChangeEmitter
|
||||
];
|
||||
|
||||
constructor(
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@@ -79,8 +89,19 @@ export class RemoteFileDialog {
|
||||
this.contextKey = RemoteFileDialogContext.bindTo(contextKeyService);
|
||||
}
|
||||
|
||||
set busy(busy: boolean) {
|
||||
if (this.filePickBox.busy !== busy) {
|
||||
this.filePickBox.busy = busy;
|
||||
this.onBusyChangeEmitter.fire(busy);
|
||||
}
|
||||
}
|
||||
|
||||
get busy(): boolean {
|
||||
return this.filePickBox.busy;
|
||||
}
|
||||
|
||||
public async showOpenDialog(options: IOpenDialogOptions = {}): Promise<URI | undefined> {
|
||||
this.scheme = this.getScheme(options.defaultUri, options.availableFileSystems);
|
||||
this.scheme = this.getScheme(options.availableFileSystems);
|
||||
this.userHome = await this.getUserHome();
|
||||
const newOptions = await this.getOptions(options);
|
||||
if (!newOptions) {
|
||||
@@ -91,7 +112,7 @@ export class RemoteFileDialog {
|
||||
}
|
||||
|
||||
public async showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined> {
|
||||
this.scheme = this.getScheme(options.defaultUri, options.availableFileSystems);
|
||||
this.scheme = this.getScheme(options.availableFileSystems);
|
||||
this.userHome = await this.getUserHome();
|
||||
this.requiresTrailing = true;
|
||||
const newOptions = await this.getOptions(options, true);
|
||||
@@ -110,9 +131,13 @@ export class RemoteFileDialog {
|
||||
}
|
||||
|
||||
private getOptions(options: ISaveDialogOptions | IOpenDialogOptions, isSave: boolean = false): IOpenDialogOptions | undefined {
|
||||
let defaultUri = options.defaultUri;
|
||||
const filename = (defaultUri && isSave && (resources.dirname(defaultUri).path === '/')) ? resources.basename(defaultUri) : undefined;
|
||||
if (!defaultUri || filename) {
|
||||
let defaultUri: URI | undefined = undefined;
|
||||
let filename: string | undefined = undefined;
|
||||
if (options.defaultUri) {
|
||||
defaultUri = (this.scheme === options.defaultUri.scheme) ? options.defaultUri : undefined;
|
||||
filename = isSave ? resources.basename(options.defaultUri) : undefined;
|
||||
}
|
||||
if (!defaultUri) {
|
||||
defaultUri = this.userHome;
|
||||
if (filename) {
|
||||
defaultUri = resources.joinPath(defaultUri, filename);
|
||||
@@ -132,8 +157,8 @@ export class RemoteFileDialog {
|
||||
return resources.toLocalResource(URI.from({ scheme: this.scheme, path }), this.scheme === Schemas.file ? undefined : this.remoteAuthority);
|
||||
}
|
||||
|
||||
private getScheme(defaultUri: URI | undefined, available: string[] | undefined): string {
|
||||
return defaultUri ? defaultUri.scheme : (available ? available[0] : Schemas.file);
|
||||
private getScheme(available: string[] | undefined): string {
|
||||
return available ? available[0] : Schemas.file;
|
||||
}
|
||||
|
||||
private async getRemoteAgentEnvironment(): Promise<IRemoteAgentEnvironment | null> {
|
||||
@@ -185,15 +210,20 @@ export class RemoteFileDialog {
|
||||
|
||||
return new Promise<URI | undefined>(async (resolve) => {
|
||||
this.filePickBox = this.quickInputService.createQuickPick<FileQuickPickItem>();
|
||||
this.filePickBox.busy = true;
|
||||
this.busy = true;
|
||||
this.filePickBox.matchOnLabel = false;
|
||||
this.filePickBox.autoFocusOnList = false;
|
||||
this.filePickBox.ignoreFocusOut = true;
|
||||
this.filePickBox.ok = true;
|
||||
if (this.options && this.options.availableFileSystems && (this.options.availableFileSystems.length > 1)) {
|
||||
if (this.options && this.options.availableFileSystems && (this.options.availableFileSystems.length > 1) && (this.options.availableFileSystems.indexOf(Schemas.file) > -1)) {
|
||||
this.filePickBox.customButton = true;
|
||||
this.filePickBox.customLabel = nls.localize('remoteFileDialog.local', 'Show Local');
|
||||
const action = this.allowFileSelection ? (this.allowFolderSelection ? OpenLocalFileFolderAction : OpenLocalFileAction) : OpenLocalFolderAction;
|
||||
let action;
|
||||
if (isSave) {
|
||||
action = SaveLocalFileCommand;
|
||||
} else {
|
||||
action = this.allowFileSelection ? (this.allowFolderSelection ? OpenLocalFileFolderCommand : OpenLocalFileCommand) : OpenLocalFolderCommand;
|
||||
}
|
||||
const keybinding = this.keybindingService.lookupKeybinding(action.ID);
|
||||
if (keybinding) {
|
||||
const label = keybinding.getLabel();
|
||||
@@ -203,9 +233,9 @@ export class RemoteFileDialog {
|
||||
}
|
||||
}
|
||||
|
||||
let isResolving = false;
|
||||
let isResolving: number = 0;
|
||||
let isAcceptHandled = false;
|
||||
this.currentFolder = homedir;
|
||||
this.currentFolder = resources.dirname(homedir);
|
||||
this.userEnteredPathSegment = '';
|
||||
this.autoCompletePathSegment = '';
|
||||
|
||||
@@ -215,24 +245,27 @@ export class RemoteFileDialog {
|
||||
this.filePickBox.items = [];
|
||||
|
||||
function doResolve(dialog: RemoteFileDialog, uri: URI | undefined) {
|
||||
if (uri) {
|
||||
uri = resources.removeTrailingPathSeparator(uri);
|
||||
}
|
||||
resolve(uri);
|
||||
dialog.contextKey.set(false);
|
||||
dialog.filePickBox.dispose();
|
||||
dispose(dialog.disposables);
|
||||
}
|
||||
|
||||
this.filePickBox.onDidCustom(() => {
|
||||
if (isAcceptHandled || this.filePickBox.busy) {
|
||||
if (isAcceptHandled || this.busy) {
|
||||
return undefined; // {{SQL CARBON EDIT}} @todo anthonydresser return to return; when we do strict null checks
|
||||
}
|
||||
|
||||
isAcceptHandled = true;
|
||||
isResolving = true;
|
||||
isResolving++;
|
||||
if (this.options.availableFileSystems && (this.options.availableFileSystems.length > 1)) {
|
||||
this.options.availableFileSystems.shift();
|
||||
}
|
||||
this.options.defaultUri = undefined;
|
||||
this.filePickBox.hide();
|
||||
if (this.requiresTrailing) {
|
||||
if (isSave) {
|
||||
return this.fileDialogService.showSaveDialog(this.options).then(result => {
|
||||
doResolve(this, result);
|
||||
});
|
||||
@@ -243,38 +276,56 @@ export class RemoteFileDialog {
|
||||
}
|
||||
});
|
||||
|
||||
this.filePickBox.onDidAccept(_ => {
|
||||
if (isAcceptHandled || this.filePickBox.busy) {
|
||||
function handleAccept(dialog: RemoteFileDialog) {
|
||||
if (dialog.busy) {
|
||||
// Save the accept until the file picker is not busy.
|
||||
dialog.onBusyChangeEmitter.event((busy: boolean) => {
|
||||
if (!busy) {
|
||||
handleAccept(dialog);
|
||||
}
|
||||
});
|
||||
return;
|
||||
} else if (isAcceptHandled) {
|
||||
return;
|
||||
}
|
||||
|
||||
isAcceptHandled = true;
|
||||
isResolving = true;
|
||||
this.onDidAccept().then(resolveValue => {
|
||||
isResolving++;
|
||||
dialog.onDidAccept().then(resolveValue => {
|
||||
if (resolveValue) {
|
||||
this.filePickBox.hide();
|
||||
doResolve(this, resolveValue);
|
||||
} else if (this.hidden) {
|
||||
doResolve(this, undefined);
|
||||
dialog.filePickBox.hide();
|
||||
doResolve(dialog, resolveValue);
|
||||
} else if (dialog.hidden) {
|
||||
doResolve(dialog, undefined);
|
||||
} else {
|
||||
isResolving = false;
|
||||
isResolving--;
|
||||
isAcceptHandled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.filePickBox.onDidAccept(_ => {
|
||||
handleAccept(this);
|
||||
});
|
||||
|
||||
this.filePickBox.onDidChangeActive(i => {
|
||||
isAcceptHandled = false;
|
||||
// update input box to match the first selected item
|
||||
if ((i.length === 1) && this.isChangeFromUser()) {
|
||||
if ((i.length === 1) && this.isSelectionChangeFromUser()) {
|
||||
this.filePickBox.validationMessage = undefined;
|
||||
this.setAutoComplete(this.constructFullUserPath(), this.userEnteredPathSegment, i[0], true);
|
||||
const userPath = this.constructFullUserPath();
|
||||
if (!equalsIgnoreCase(this.filePickBox.value.substring(0, userPath.length), userPath)) {
|
||||
this.filePickBox.valueSelection = [0, this.filePickBox.value.length];
|
||||
this.insertText(userPath, userPath);
|
||||
}
|
||||
this.setAutoComplete(userPath, this.userEnteredPathSegment, i[0], true);
|
||||
}
|
||||
});
|
||||
|
||||
this.filePickBox.onDidChangeValue(async value => {
|
||||
try {
|
||||
// onDidChangeValue can also be triggered by the auto complete, so if it looks like the auto complete, don't do anything
|
||||
if (this.isChangeFromUser()) {
|
||||
if (this.isValueChangeFromUser()) {
|
||||
// If the user has just entered more bad path, don't change anything
|
||||
if (!equalsIgnoreCase(value, this.constructFullUserPath()) && !this.isBadSubpath(value)) {
|
||||
this.filePickBox.validationMessage = undefined;
|
||||
@@ -288,6 +339,7 @@ export class RemoteFileDialog {
|
||||
}
|
||||
} else {
|
||||
this.filePickBox.activeItems = [];
|
||||
this.userEnteredPathSegment = '';
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
@@ -296,7 +348,7 @@ export class RemoteFileDialog {
|
||||
});
|
||||
this.filePickBox.onDidHide(() => {
|
||||
this.hidden = true;
|
||||
if (!isResolving) {
|
||||
if (isResolving === 0) {
|
||||
doResolve(this, undefined);
|
||||
}
|
||||
});
|
||||
@@ -309,7 +361,7 @@ export class RemoteFileDialog {
|
||||
} else {
|
||||
this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length];
|
||||
}
|
||||
this.filePickBox.busy = false;
|
||||
this.busy = false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -317,16 +369,27 @@ export class RemoteFileDialog {
|
||||
return this.badPath && (value.length > this.badPath.length) && equalsIgnoreCase(value.substring(0, this.badPath.length), this.badPath);
|
||||
}
|
||||
|
||||
private isChangeFromUser(): boolean {
|
||||
if (equalsIgnoreCase(this.filePickBox.value, this.pathAppend(this.currentFolder, this.userEnteredPathSegment + this.autoCompletePathSegment))
|
||||
&& (this.activeItem === (this.filePickBox.activeItems ? this.filePickBox.activeItems[0] : undefined))) {
|
||||
private isValueChangeFromUser(): boolean {
|
||||
if (equalsIgnoreCase(this.filePickBox.value, this.pathAppend(this.currentFolder, this.userEnteredPathSegment + this.autoCompletePathSegment))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private isSelectionChangeFromUser(): boolean {
|
||||
if (this.activeItem === (this.filePickBox.activeItems ? this.filePickBox.activeItems[0] : undefined)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private constructFullUserPath(): string {
|
||||
return this.pathAppend(this.currentFolder, this.userEnteredPathSegment);
|
||||
const currentFolderPath = this.pathFromUri(this.currentFolder);
|
||||
if (equalsIgnoreCase(this.filePickBox.value.substr(0, this.userEnteredPathSegment.length), this.userEnteredPathSegment) && equalsIgnoreCase(this.filePickBox.value.substr(0, currentFolderPath.length), currentFolderPath)) {
|
||||
return currentFolderPath;
|
||||
} else {
|
||||
return this.pathAppend(this.currentFolder, this.userEnteredPathSegment);
|
||||
}
|
||||
}
|
||||
|
||||
private filePickBoxValue(): URI {
|
||||
@@ -340,14 +403,19 @@ export class RemoteFileDialog {
|
||||
const relativePath = resources.relativePath(currentDisplayUri, directUri);
|
||||
const isSameRoot = (this.filePickBox.value.length > 1 && currentPath.length > 1) ? equalsIgnoreCase(this.filePickBox.value.substr(0, 2), currentPath.substr(0, 2)) : false;
|
||||
if (relativePath && isSameRoot) {
|
||||
return resources.joinPath(this.currentFolder, relativePath);
|
||||
let path = resources.joinPath(this.currentFolder, relativePath);
|
||||
const directBasename = resources.basename(directUri);
|
||||
if ((directBasename === '.') || (directBasename === '..')) {
|
||||
path = this.remoteUriFrom(this.pathAppend(path, directBasename));
|
||||
}
|
||||
return resources.hasTrailingPathSeparator(directUri) ? resources.addTrailingPathSeparator(path) : path;
|
||||
} else {
|
||||
return directUri;
|
||||
}
|
||||
}
|
||||
|
||||
private async onDidAccept(): Promise<URI | undefined> {
|
||||
this.filePickBox.busy = true;
|
||||
this.busy = true;
|
||||
if (this.filePickBox.activeItems.length === 1) {
|
||||
const item = this.filePickBox.selectedItems[0];
|
||||
if (item.isFolder) {
|
||||
@@ -357,10 +425,9 @@ export class RemoteFileDialog {
|
||||
// When possible, cause the update to happen by modifying the input box.
|
||||
// This allows all input box updates to happen first, and uses the same code path as the user typing.
|
||||
const newPath = this.pathFromUri(item.uri);
|
||||
if (startsWithIgnoreCase(newPath, this.filePickBox.value)) {
|
||||
const insertValue = newPath.substring(this.filePickBox.value.length, newPath.length);
|
||||
this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length];
|
||||
this.insertText(newPath, insertValue);
|
||||
if (startsWithIgnoreCase(newPath, this.filePickBox.value) && (equalsIgnoreCase(item.label, resources.basename(item.uri)))) {
|
||||
this.filePickBox.valueSelection = [this.pathFromUri(this.currentFolder).length, this.filePickBox.value.length];
|
||||
this.insertText(newPath, item.label);
|
||||
} else if ((item.label === '..') && startsWithIgnoreCase(this.filePickBox.value, newPath)) {
|
||||
this.filePickBox.valueSelection = [newPath.length, this.filePickBox.value.length];
|
||||
this.insertText(newPath, '');
|
||||
@@ -368,11 +435,13 @@ export class RemoteFileDialog {
|
||||
await this.updateItems(item.uri, true);
|
||||
}
|
||||
}
|
||||
this.filePickBox.busy = false;
|
||||
return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check
|
||||
}
|
||||
} else {
|
||||
// If the items have updated, don't try to resolve
|
||||
if ((await this.tryUpdateItems(this.filePickBox.value, this.filePickBoxValue())) !== UpdateResult.NotUpdated) {
|
||||
this.filePickBox.busy = false;
|
||||
return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check
|
||||
}
|
||||
}
|
||||
@@ -388,10 +457,10 @@ export class RemoteFileDialog {
|
||||
resolveValue = this.addPostfix(resolveValue);
|
||||
}
|
||||
if (await this.validate(resolveValue)) {
|
||||
this.filePickBox.busy = false;
|
||||
this.busy = false;
|
||||
return resolveValue;
|
||||
}
|
||||
this.filePickBox.busy = false;
|
||||
this.busy = false;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -429,7 +498,7 @@ export class RemoteFileDialog {
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
if (statWithoutTrailing && statWithoutTrailing.isDirectory && (resources.basename(valueUri) !== '.')) {
|
||||
if (statWithoutTrailing && statWithoutTrailing.isDirectory) {
|
||||
await this.updateItems(inputUriDirname, false, resources.basename(valueUri));
|
||||
this.badPath = undefined;
|
||||
return UpdateResult.Updated;
|
||||
@@ -447,11 +516,13 @@ export class RemoteFileDialog {
|
||||
const userPath = this.constructFullUserPath();
|
||||
if (equalsIgnoreCase(userPath, value.substring(0, userPath.length))) {
|
||||
let hasMatch = false;
|
||||
for (let i = 0; i < this.filePickBox.items.length; i++) {
|
||||
const item = <FileQuickPickItem>this.filePickBox.items[i];
|
||||
if (this.setAutoComplete(value, inputBasename, item)) {
|
||||
hasMatch = true;
|
||||
break;
|
||||
if (inputBasename.length > this.userEnteredPathSegment.length) {
|
||||
for (let i = 0; i < this.filePickBox.items.length; i++) {
|
||||
const item = <FileQuickPickItem>this.filePickBox.items[i];
|
||||
if (this.setAutoComplete(value, inputBasename, item)) {
|
||||
hasMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasMatch) {
|
||||
@@ -460,17 +531,13 @@ export class RemoteFileDialog {
|
||||
this.filePickBox.activeItems = [];
|
||||
}
|
||||
} else {
|
||||
if (!equalsIgnoreCase(inputBasename, resources.basename(this.currentFolder))) {
|
||||
this.userEnteredPathSegment = inputBasename;
|
||||
} else {
|
||||
this.userEnteredPathSegment = '';
|
||||
}
|
||||
this.userEnteredPathSegment = inputBasename;
|
||||
this.autoCompletePathSegment = '';
|
||||
}
|
||||
}
|
||||
|
||||
private setAutoComplete(startingValue: string, startingBasename: string, quickPickItem: FileQuickPickItem, force: boolean = false): boolean {
|
||||
if (this.filePickBox.busy) {
|
||||
if (this.busy) {
|
||||
// We're in the middle of something else. Doing an auto complete now can result jumbled or incorrect autocompletes.
|
||||
this.userEnteredPathSegment = startingBasename;
|
||||
this.autoCompletePathSegment = '';
|
||||
@@ -480,7 +547,7 @@ export class RemoteFileDialog {
|
||||
// Either force the autocomplete, or the old value should be one smaller than the new value and match the new value.
|
||||
if (itemBasename === '..') {
|
||||
// Don't match on the up directory item ever.
|
||||
this.userEnteredPathSegment = startingValue;
|
||||
this.userEnteredPathSegment = '';
|
||||
this.autoCompletePathSegment = '';
|
||||
this.activeItem = quickPickItem;
|
||||
if (force) {
|
||||
@@ -494,9 +561,6 @@ export class RemoteFileDialog {
|
||||
// Changing the active items will trigger the onDidActiveItemsChanged. Clear the autocomplete first, then set it after.
|
||||
this.autoCompletePathSegment = '';
|
||||
this.filePickBox.activeItems = [quickPickItem];
|
||||
this.autoCompletePathSegment = this.trimTrailingSlash(itemBasename.substr(startingBasename.length));
|
||||
this.insertText(startingValue + this.autoCompletePathSegment, this.autoCompletePathSegment);
|
||||
this.filePickBox.valueSelection = [startingValue.length, this.filePickBox.value.length];
|
||||
return true;
|
||||
} else if (force && (!equalsIgnoreCase(quickPickItem.label, (this.userEnteredPathSegment + this.autoCompletePathSegment)))) {
|
||||
this.userEnteredPathSegment = '';
|
||||
@@ -641,30 +705,46 @@ export class RemoteFileDialog {
|
||||
}
|
||||
|
||||
private async updateItems(newFolder: URI, force: boolean = false, trailing?: string) {
|
||||
this.filePickBox.busy = true;
|
||||
this.busy = true;
|
||||
this.userEnteredPathSegment = trailing ? trailing : '';
|
||||
this.autoCompletePathSegment = '';
|
||||
const newValue = trailing ? this.pathFromUri(resources.joinPath(newFolder, trailing)) : this.pathFromUri(newFolder, true);
|
||||
const newValue = trailing ? this.pathAppend(newFolder, trailing) : this.pathFromUri(newFolder, true);
|
||||
this.currentFolder = resources.addTrailingPathSeparator(newFolder, this.separator);
|
||||
return this.createItems(this.currentFolder).then(items => {
|
||||
this.filePickBox.items = items;
|
||||
if (this.allowFolderSelection) {
|
||||
this.filePickBox.activeItems = [];
|
||||
}
|
||||
// the user might have continued typing while we were updating. Only update the input box if it doesn't match the directory.
|
||||
if (!equalsIgnoreCase(this.filePickBox.value, newValue) && force) {
|
||||
this.filePickBox.valueSelection = [0, this.filePickBox.value.length];
|
||||
this.insertText(newValue, newValue);
|
||||
}
|
||||
if (force && trailing) {
|
||||
// Keep the cursor position in front of the save as name.
|
||||
this.filePickBox.valueSelection = [this.filePickBox.value.length - trailing.length, this.filePickBox.value.length - trailing.length];
|
||||
} else if (!trailing) {
|
||||
// If there is trailing, we don't move the cursor. If there is no trailing, cursor goes at the end.
|
||||
this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length];
|
||||
}
|
||||
this.filePickBox.busy = false;
|
||||
|
||||
const updatingPromise = createCancelablePromise(async token => {
|
||||
return this.createItems(this.currentFolder, token).then(items => {
|
||||
if (token.isCancellationRequested) {
|
||||
this.busy = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.filePickBox.items = items;
|
||||
if (this.allowFolderSelection) {
|
||||
this.filePickBox.activeItems = [];
|
||||
}
|
||||
// the user might have continued typing while we were updating. Only update the input box if it doesn't matche directory.
|
||||
if (!equalsIgnoreCase(this.filePickBox.value, newValue) && force) {
|
||||
this.filePickBox.valueSelection = [0, this.filePickBox.value.length];
|
||||
this.insertText(newValue, newValue);
|
||||
}
|
||||
if (force && trailing) {
|
||||
// Keep the cursor position in front of the save as name.
|
||||
this.filePickBox.valueSelection = [this.filePickBox.value.length - trailing.length, this.filePickBox.value.length - trailing.length];
|
||||
} else if (!trailing) {
|
||||
// If there is trailing, we don't move the cursor. If there is no trailing, cursor goes at the end.
|
||||
this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length];
|
||||
}
|
||||
this.busy = false;
|
||||
this.updatingPromise = undefined;
|
||||
});
|
||||
});
|
||||
|
||||
if (this.updatingPromise !== undefined) {
|
||||
this.updatingPromise.cancel();
|
||||
}
|
||||
this.updatingPromise = updatingPromise;
|
||||
|
||||
return updatingPromise;
|
||||
}
|
||||
|
||||
private pathFromUri(uri: URI, endWithSeparator: boolean = false): string {
|
||||
@@ -683,7 +763,7 @@ export class RemoteFileDialog {
|
||||
private pathAppend(uri: URI, additional: string): string {
|
||||
if ((additional === '..') || (additional === '.')) {
|
||||
const basePath = this.pathFromUri(uri);
|
||||
return basePath + (this.endsWithSlash(basePath) ? '' : this.separator) + additional;
|
||||
return basePath + this.separator + additional;
|
||||
} else {
|
||||
return this.pathFromUri(resources.joinPath(uri, additional));
|
||||
}
|
||||
@@ -716,14 +796,14 @@ export class RemoteFileDialog {
|
||||
return null;
|
||||
}
|
||||
|
||||
private async createItems(currentFolder: URI): Promise<FileQuickPickItem[]> {
|
||||
private async createItems(currentFolder: URI, token: CancellationToken): Promise<FileQuickPickItem[]> {
|
||||
const result: FileQuickPickItem[] = [];
|
||||
|
||||
const backDir = this.createBackItem(currentFolder);
|
||||
try {
|
||||
const folder = await this.fileService.resolve(currentFolder);
|
||||
const fileNames = folder.children ? folder.children.map(child => child.name) : [];
|
||||
const items = await Promise.all(fileNames.map(fileName => this.createItem(fileName, currentFolder)));
|
||||
const items = await Promise.all(fileNames.map(fileName => this.createItem(fileName, currentFolder, token)));
|
||||
for (let item of items) {
|
||||
if (item) {
|
||||
result.push(item);
|
||||
@@ -733,6 +813,9 @@ export class RemoteFileDialog {
|
||||
// ignore
|
||||
console.log(e);
|
||||
}
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
const sorted = result.sort((i1, i2) => {
|
||||
if (i1.isFolder !== i2.isFolder) {
|
||||
return i1.isFolder ? -1 : 1;
|
||||
@@ -763,7 +846,10 @@ export class RemoteFileDialog {
|
||||
return true;
|
||||
}
|
||||
|
||||
private async createItem(filename: string, parent: URI): Promise<FileQuickPickItem | undefined> {
|
||||
private async createItem(filename: string, parent: URI, token: CancellationToken): Promise<FileQuickPickItem | undefined> {
|
||||
if (token.isCancellationRequested) {
|
||||
return undefined;
|
||||
}
|
||||
let fullPath = resources.joinPath(parent, filename);
|
||||
try {
|
||||
const stat = await this.fileService.resolve(fullPath);
|
||||
|
||||
Reference in New Issue
Block a user