diff --git a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts b/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts index 8d30f12cb1bde709331f45cf906c6a4ae7b8526c..fb7e1b0040fbb560197f4fa31bc527c2225ffef3 100644 --- a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts @@ -22,11 +22,11 @@ import { Schemas } from 'vs/base/common/network'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys'; -import { equalsIgnoreCase, format } from 'vs/base/common/strings'; +import { equalsIgnoreCase, format, startsWithIgnoreCase } from 'vs/base/common/strings'; import { OpenLocalFileAction, OpenLocalFileFolderAction, OpenLocalFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; +import { RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys'; interface FileQuickPickItem extends IQuickPickItem { uri: URI; @@ -63,6 +63,7 @@ export class RemoteFileDialog { private userHome: URI; private badPath: string | undefined; private remoteAgentEnvironment: IRemoteAgentEnvironment | null; + private separator: string; constructor( @IFileService private readonly fileService: IFileService, @@ -159,6 +160,7 @@ export class RemoteFileDialog { private async pickResource(isSave: boolean = false): Promise { this.allowFolderSelection = !!this.options.canSelectFolders; this.allowFileSelection = !!this.options.canSelectFiles; + this.separator = this.labelService.getSeparator(this.scheme, this.remoteAuthority); this.hidden = false; let homedir: URI = this.options.defaultUri ? this.options.defaultUri : this.workspaceContextService.getWorkspace().folders[0].uri; let stat: IFileStat | undefined; @@ -306,7 +308,7 @@ export class RemoteFileDialog { this.filePickBox.show(); this.contextKey.set(true); - await this.updateItems(homedir, false, this.trailing); + await this.updateItems(homedir, true, this.trailing); if (this.trailing) { this.filePickBox.valueSelection = [this.filePickBox.value.length - this.trailing.length, this.filePickBox.value.length - ext.length]; } else { @@ -350,21 +352,36 @@ export class RemoteFileDialog { } private async onDidAccept(): Promise { - let updateString: string; - let updateUri: URI; + this.filePickBox.busy = true; if (this.filePickBox.activeItems.length === 1) { - updateUri = this.filePickBox.selectedItems[0].uri; - updateString = this.pathFromUri(updateUri); + const item = this.filePickBox.selectedItems[0]; + if (item.isFolder) { + if (this.trailing) { + await this.updateItems(item.uri, true, this.trailing); + } else { + // 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); + } else if ((item.label === '..') && startsWithIgnoreCase(this.filePickBox.value, newPath)) { + this.filePickBox.valueSelection = [newPath.length, this.filePickBox.value.length]; + this.insertText(newPath, ''); + } else { + await this.updateItems(item.uri, true); + } + } + return; + } } else { - updateString = this.filePickBox.value; - updateUri = this.filePickBoxValue(); - } - // If the items have updated, don't try to resolve - if ((await this.tryUpdateItems(updateString, updateUri)) !== UpdateResult.NotUpdated) { - return; + // If the items have updated, don't try to resolve + if ((await this.tryUpdateItems(this.filePickBox.value, this.filePickBoxValue())) !== UpdateResult.NotUpdated) { + return; + } } - this.filePickBox.busy = true; let resolveValue: URI | undefined; // Find resolve value if (this.filePickBox.activeItems.length === 0) { @@ -384,10 +401,7 @@ export class RemoteFileDialog { } private async tryUpdateItems(value: string, valueUri: URI): Promise { - if (this.filePickBox.busy) { - this.badPath = undefined; - return UpdateResult.Updating; - } else if ((value.length > 0) && ((value[value.length - 1] === '~') || (value[0] === '~'))) { + if ((value.length > 0) && ((value[value.length - 1] === '~') || (value[0] === '~'))) { let newDir = this.userHome; if ((value[0] === '~') && (value.length > 1)) { newDir = resources.joinPath(newDir, value.substring(1)); @@ -636,7 +650,7 @@ export class RemoteFileDialog { return uri; } else { const dir = resources.dirname(uri); - const base = resources.basename(uri) + this.labelService.getSeparator(uri.scheme, uri.authority); + const base = resources.basename(uri) + this.separator; return resources.joinPath(dir, base); } } @@ -646,24 +660,16 @@ export class RemoteFileDialog { this.userEnteredPathSegment = trailing ? trailing : ''; this.autoCompletePathSegment = ''; const newValue = trailing ? this.pathFromUri(resources.joinPath(newFolder, trailing)) : this.pathFromUri(newFolder, true); - const oldFolder = this.currentFolder; - const newFolderPath = this.pathFromUri(newFolder, true); this.currentFolder = this.ensureTrailingSeparator(newFolder); return this.createItems(this.currentFolder).then(items => { this.filePickBox.items = items; if (this.allowFolderSelection) { this.filePickBox.activeItems = []; } - if (!equalsIgnoreCase(this.filePickBox.value, newValue)) { - // 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.substring(0, newValue.length), newValue)) { - this.filePickBox.valueSelection = [0, this.filePickBox.value.length]; - this.insertText(newValue, newValue); - } else if (force || equalsIgnoreCase(this.pathFromUri(resources.dirname(oldFolder), true), newFolderPath)) { - // This is the case where the user went up one dir or is clicking on dirs. We need to make sure that we remove the final dir. - this.filePickBox.valueSelection = [newFolderPath.length, this.filePickBox.value.length]; - this.insertText(newValue, ''); - } + // 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. @@ -676,15 +682,14 @@ export class RemoteFileDialog { } private pathFromUri(uri: URI, endWithSeparator: boolean = false): string { - const sep = this.labelService.getSeparator(uri.scheme, uri.authority); let result: string = uri.fsPath.replace(/\n/g, ''); - if (sep === '/') { - result = result.replace(/\\/g, sep); + if (this.separator === '/') { + result = result.replace(/\\/g, this.separator); } else { - result = result.replace(/\//g, sep); + result = result.replace(/\//g, this.separator); } if (endWithSeparator && !this.endsWithSlash(result)) { - result = result + sep; + result = result + this.separator; } return result; } @@ -692,7 +697,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.labelService.getSeparator(uri.scheme, uri.authority)) + additional; + return basePath + (this.endsWithSlash(basePath) ? '' : this.separator) + additional; } else { return this.pathFromUri(resources.joinPath(uri, additional)); }