mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-19 17:22:48 -05:00
Merge from vscode 31e03b8ffbb218a87e3941f2b63a249f061fe0e4 (#4986)
This commit is contained in:
@@ -12,7 +12,6 @@ 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 } 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';
|
||||
@@ -20,10 +19,11 @@ 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 { 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/common/contextkeys';
|
||||
import { equalsIgnoreCase } from 'vs/base/common/strings';
|
||||
|
||||
interface FileQuickPickItem extends IQuickPickItem {
|
||||
uri: URI;
|
||||
@@ -45,28 +45,29 @@ export class RemoteFileDialog {
|
||||
private allowFolderSelection: boolean;
|
||||
private remoteAuthority: string | undefined;
|
||||
private requiresTrailing: boolean;
|
||||
private userValue: string;
|
||||
private scheme: string = REMOTE_HOST_SCHEME;
|
||||
private shouldOverwriteFile: boolean = false;
|
||||
private autoComplete: string;
|
||||
private contextKey: IContextKey<boolean>;
|
||||
|
||||
private userEnteredPathSegment: string;
|
||||
private autoCompletePathSegment: string;
|
||||
private activeItem: FileQuickPickItem;
|
||||
|
||||
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,
|
||||
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
|
||||
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService
|
||||
|
||||
) {
|
||||
this.remoteAuthority = this.windowService.getConfiguration().remoteAuthority;
|
||||
this.remoteAuthority = this.environmentService.configuration.remoteAuthority;
|
||||
this.contextKey = RemoteFileDialogContext.bindTo(contextKeyService);
|
||||
}
|
||||
|
||||
@@ -134,7 +135,7 @@ export class RemoteFileDialog {
|
||||
|
||||
private remoteUriFrom(path: string): URI {
|
||||
path = path.replace(/\\/g, '/');
|
||||
return URI.from({ scheme: this.scheme, authority: this.remoteAuthority, path });
|
||||
return resources.toLocalResource(URI.from({ scheme: this.scheme, path }), this.remoteAuthority);
|
||||
}
|
||||
|
||||
private getScheme(defaultUri: URI | undefined, available: string[] | undefined): string {
|
||||
@@ -179,7 +180,7 @@ export class RemoteFileDialog {
|
||||
}
|
||||
this.acceptButton = { iconPath: this.getDialogIcons('accept'), tooltip: this.options.title };
|
||||
|
||||
return new Promise<URI | undefined>((resolve) => {
|
||||
return new Promise<URI | undefined>(async (resolve) => {
|
||||
this.filePickBox = this.quickInputService.createQuickPick<FileQuickPickItem>();
|
||||
this.filePickBox.matchOnLabel = false;
|
||||
this.filePickBox.autoFocusOnList = false;
|
||||
@@ -187,6 +188,8 @@ export class RemoteFileDialog {
|
||||
let isResolving = false;
|
||||
let isAcceptHandled = false;
|
||||
this.currentFolder = homedir;
|
||||
this.userEnteredPathSegment = '';
|
||||
this.autoCompletePathSegment = '';
|
||||
this.filePickBox.buttons = [this.acceptButton];
|
||||
this.filePickBox.onDidTriggerButton(_ => {
|
||||
// accept button
|
||||
@@ -232,22 +235,25 @@ export class RemoteFileDialog {
|
||||
this.filePickBox.onDidChangeActive(i => {
|
||||
isAcceptHandled = false;
|
||||
// update input box to match the first selected item
|
||||
if (i.length === 1) {
|
||||
this.setAutoComplete(this.userValue, resources.basename(this.remoteUriFrom(this.userValue)), i[0], true);
|
||||
if ((i.length === 1) && this.isChangeFromUser()) {
|
||||
this.setAutoComplete(this.constructFullUserPath(), this.userEnteredPathSegment, i[0], true);
|
||||
}
|
||||
});
|
||||
|
||||
this.filePickBox.onDidChangeValue(async value => {
|
||||
// onDidChangeValue can also be triggered by the auto complete, so if it looks like the auto complete, don't do anything
|
||||
if (!this.autoComplete || (value !== this.autoComplete)) {
|
||||
if (value !== this.userValue) {
|
||||
if (this.isChangeFromUser()) {
|
||||
if (value !== this.constructFullUserPath()) {
|
||||
this.filePickBox.validationMessage = undefined;
|
||||
this.shouldOverwriteFile = false;
|
||||
const valueUri = this.remoteUriFrom(this.trimTrailingSlash(this.filePickBox.value));
|
||||
let isUpdate = false;
|
||||
if (!resources.isEqual(this.remoteUriFrom(this.trimTrailingSlash(this.pathFromUri(this.currentFolder))), valueUri, true)) {
|
||||
await this.tryUpdateItems(value, this.remoteUriFrom(this.filePickBox.value));
|
||||
isUpdate = await this.tryUpdateItems(value, this.remoteUriFrom(this.filePickBox.value));
|
||||
}
|
||||
if (!isUpdate) {
|
||||
this.setActiveItems(value);
|
||||
}
|
||||
this.setActiveItems(value);
|
||||
} else {
|
||||
this.filePickBox.activeItems = [];
|
||||
}
|
||||
@@ -262,16 +268,27 @@ export class RemoteFileDialog {
|
||||
|
||||
this.filePickBox.show();
|
||||
this.contextKey.set(true);
|
||||
this.updateItems(homedir, trailing);
|
||||
await 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 isChangeFromUser(): boolean {
|
||||
if ((this.filePickBox.value === this.pathAppend(this.currentFolder, this.userEnteredPathSegment + this.autoCompletePathSegment))
|
||||
&& (this.activeItem === (this.filePickBox.activeItems ? this.filePickBox.activeItems[0] : undefined))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private constructFullUserPath(): string {
|
||||
return this.pathAppend(this.currentFolder, this.userEnteredPathSegment);
|
||||
}
|
||||
|
||||
private async onDidAccept(): Promise<URI | undefined> {
|
||||
// Check if Open Local has been selected
|
||||
const selectedItems: ReadonlyArray<FileQuickPickItem> = this.filePickBox.selectedItems;
|
||||
@@ -279,6 +296,7 @@ export class RemoteFileDialog {
|
||||
if (this.options.availableFileSystems && (this.options.availableFileSystems.length > 1)) {
|
||||
this.options.availableFileSystems.shift();
|
||||
}
|
||||
this.options.defaultUri = undefined;
|
||||
if (this.requiresTrailing) {
|
||||
return this.fileDialogService.showSaveDialog(this.options).then(result => {
|
||||
return result;
|
||||
@@ -331,14 +349,14 @@ export class RemoteFileDialog {
|
||||
}
|
||||
} else if (navigateValue) {
|
||||
// Try to navigate into the folder
|
||||
this.updateItems(navigateValue);
|
||||
await this.updateItems(navigateValue);
|
||||
} else {
|
||||
// validation error. Path does not exist.
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
private async tryUpdateItems(value: string, valueUri: URI) {
|
||||
private async tryUpdateItems(value: string, valueUri: URI): Promise<boolean> {
|
||||
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 {
|
||||
@@ -346,11 +364,12 @@ export class RemoteFileDialog {
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
if (stat && stat.isDirectory && (resources.basename(valueUri) !== '.')) {
|
||||
this.updateItems(valueUri);
|
||||
if (stat && stat.isDirectory && (resources.basename(valueUri) !== '.') && this.endsWithSlash(value)) {
|
||||
await this.updateItems(valueUri);
|
||||
return true;
|
||||
} else {
|
||||
const inputUriDirname = resources.dirname(valueUri);
|
||||
if (!resources.isEqual(this.currentFolder, inputUriDirname, true)) {
|
||||
if (!resources.isEqual(this.remoteUriFrom(this.trimTrailingSlash(this.pathFromUri(this.currentFolder))), inputUriDirname, true)) {
|
||||
let statWithoutTrailing: IFileStat | undefined;
|
||||
try {
|
||||
statWithoutTrailing = await this.fileService.resolve(inputUriDirname);
|
||||
@@ -358,59 +377,70 @@ export class RemoteFileDialog {
|
||||
// do nothing
|
||||
}
|
||||
if (statWithoutTrailing && statWithoutTrailing.isDirectory && (resources.basename(valueUri) !== '.')) {
|
||||
this.updateItems(inputUriDirname, resources.basename(valueUri));
|
||||
await this.updateItems(inputUriDirname, resources.basename(valueUri));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private setActiveItems(value: string) {
|
||||
if (!this.userValue || (value !== this.userValue.substring(0, value.length))) {
|
||||
const inputBasename = resources.basename(this.remoteUriFrom(value));
|
||||
const inputBasename = resources.basename(this.remoteUriFrom(value));
|
||||
// Make sure that the folder whose children we are currently viewing matches the path in the input
|
||||
const userPath = this.constructFullUserPath();
|
||||
if (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)) {
|
||||
this.filePickBox.activeItems = [item];
|
||||
hasMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasMatch) {
|
||||
this.userEnteredPathSegment = inputBasename;
|
||||
this.autoCompletePathSegment = '';
|
||||
this.filePickBox.activeItems = [];
|
||||
}
|
||||
} else {
|
||||
this.userValue = value;
|
||||
this.autoComplete = '';
|
||||
if (inputBasename !== resources.basename(this.currentFolder)) {
|
||||
this.userEnteredPathSegment = inputBasename;
|
||||
} else {
|
||||
this.userEnteredPathSegment = '';
|
||||
}
|
||||
this.autoCompletePathSegment = '';
|
||||
}
|
||||
}
|
||||
|
||||
private setAutoComplete(startingValue: string, startingBasename: string, quickPickItem: FileQuickPickItem, force: boolean = false): boolean {
|
||||
const itemBasename = (quickPickItem.label === '..') ? quickPickItem.label : resources.basename(quickPickItem.uri);
|
||||
const itemPathLabel = (itemBasename === '..') ? this.pathAppend(this.currentFolder, itemBasename) : this.pathFromUri(quickPickItem.uri);
|
||||
if (this.trimTrailingSlash(this.filePickBox.value) !== itemPathLabel) {
|
||||
// Either foce the autocomplete, or the old value should be one smaller than the new value and match the new value.
|
||||
if (!force && (itemBasename.length >= startingBasename.length) && (itemBasename.substr(0, startingBasename.length).toLowerCase() === startingBasename.toLowerCase())) {
|
||||
this.userValue = startingValue;
|
||||
const autoCompleteValue = itemBasename.substr(startingBasename.length);
|
||||
this.autoComplete = startingValue + autoCompleteValue;
|
||||
this.insertText(this.autoComplete, autoCompleteValue);
|
||||
this.filePickBox.valueSelection = [startingValue.length, this.filePickBox.value.length];
|
||||
return true;
|
||||
} else if (force) {
|
||||
this.userValue = this.pathFromUri(this.currentFolder, true);
|
||||
this.autoComplete = this.pathAppend(this.currentFolder, itemBasename);
|
||||
this.filePickBox.valueSelection = [this.userValue.length, this.filePickBox.value.length];
|
||||
// use insert text to preserve undo buffer
|
||||
this.insertText(this.autoComplete, itemBasename);
|
||||
this.filePickBox.valueSelection = [this.filePickBox.value.length - itemBasename.length, this.filePickBox.value.length];
|
||||
return true;
|
||||
}
|
||||
// Either force the autocomplete, or the old value should be one smaller than the new value and match the new value.
|
||||
if (!force && (itemBasename.length >= startingBasename.length) && equalsIgnoreCase(itemBasename.substr(0, startingBasename.length), startingBasename)) {
|
||||
this.userEnteredPathSegment = startingBasename;
|
||||
this.activeItem = quickPickItem;
|
||||
// Changing the active items will trigger the onDidActiveItemsChanged. Clear the autocomplete first, then set it after.
|
||||
this.autoCompletePathSegment = '';
|
||||
this.filePickBox.activeItems = [quickPickItem];
|
||||
this.autoCompletePathSegment = 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 && (quickPickItem.label !== (this.userEnteredPathSegment + this.autoCompletePathSegment))) {
|
||||
this.userEnteredPathSegment = '';
|
||||
this.autoCompletePathSegment = itemBasename;
|
||||
this.activeItem = quickPickItem;
|
||||
this.filePickBox.valueSelection = [this.pathFromUri(this.currentFolder, true).length, this.filePickBox.value.length];
|
||||
// use insert text to preserve undo buffer
|
||||
this.insertText(this.pathAppend(this.currentFolder, itemBasename), itemBasename);
|
||||
this.filePickBox.valueSelection = [this.filePickBox.value.length - itemBasename.length, this.filePickBox.value.length];
|
||||
return true;
|
||||
} else {
|
||||
this.userEnteredPathSegment = startingBasename;
|
||||
this.autoCompletePathSegment = '';
|
||||
return false;
|
||||
}
|
||||
this.userValue = startingValue;
|
||||
this.autoComplete = '';
|
||||
return false;
|
||||
}
|
||||
|
||||
private insertText(wholeValue: string, insertText: string) {
|
||||
@@ -528,15 +558,15 @@ export class RemoteFileDialog {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
private updateItems(newFolder: URI, trailing?: string) {
|
||||
this.currentFolder = newFolder;
|
||||
this.userValue = this.pathFromUri(newFolder);
|
||||
this.autoComplete = '';
|
||||
private async updateItems(newFolder: URI, trailing?: string) {
|
||||
this.userEnteredPathSegment = trailing ? trailing : '';
|
||||
this.autoCompletePathSegment = '';
|
||||
this.filePickBox.valueSelection = [0, this.filePickBox.value.length];
|
||||
const newValue = trailing ? this.pathFromUri(resources.joinPath(newFolder, trailing)) : this.pathFromUri(newFolder, true);
|
||||
this.currentFolder = this.remoteUriFrom(this.pathFromUri(newFolder, true));
|
||||
this.insertText(newValue, newValue);
|
||||
this.filePickBox.busy = true;
|
||||
this.createItems(this.currentFolder).then(items => {
|
||||
return this.createItems(this.currentFolder).then(items => {
|
||||
this.filePickBox.items = items;
|
||||
if (this.allowFolderSelection) {
|
||||
this.filePickBox.activeItems = [];
|
||||
@@ -560,8 +590,9 @@ export class RemoteFileDialog {
|
||||
}
|
||||
|
||||
private pathAppend(uri: URI, additional: string): string {
|
||||
if (additional === '..') {
|
||||
return this.pathFromUri(uri) + this.labelService.getSeparator(uri.scheme, uri.authority) + additional;
|
||||
if ((additional === '..') || (additional === '.')) {
|
||||
const basePath = this.pathFromUri(uri);
|
||||
return basePath + (this.endsWithSlash(basePath) ? '' : this.labelService.getSeparator(uri.scheme, uri.authority)) + additional;
|
||||
} else {
|
||||
return this.pathFromUri(resources.joinPath(uri, additional));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user