mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-17 23:35:54 +01:00
Fix remote mode multi-file selection via Show Local dialog (#300408)
* Initial plan * Fix remote mode multi-file selection via Show Local dialog Change ISimpleFileDialog.showOpenDialog to return URI[] instead of single URI. When 'Show Local' is clicked in remote file dialog, pass through all selected files from the native dialog instead of only taking the first result. Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com>
This commit is contained in:
@@ -229,7 +229,8 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
|
||||
const title = nls.localize('openFileOrFolder.title', 'Open File or Folder');
|
||||
const availableFileSystems = this.addFileSchemaIfNeeded(schema);
|
||||
|
||||
const uri = await this.pickResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems });
|
||||
const uris = await this.pickResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems });
|
||||
const uri = uris?.[0];
|
||||
|
||||
if (uri) {
|
||||
const stat = await this.fileService.stat(uri);
|
||||
@@ -251,7 +252,8 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
|
||||
const title = nls.localize('openFile.title', 'Open File');
|
||||
const availableFileSystems = this.addFileSchemaIfNeeded(schema);
|
||||
|
||||
const uri = await this.pickResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems });
|
||||
const uris = await this.pickResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems });
|
||||
const uri = uris?.[0];
|
||||
if (uri) {
|
||||
this.addFileToRecentlyOpened(uri);
|
||||
|
||||
@@ -271,7 +273,8 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
|
||||
const title = nls.localize('openFolder.title', 'Open Folder');
|
||||
const availableFileSystems = this.addFileSchemaIfNeeded(schema, true);
|
||||
|
||||
const uri = await this.pickResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems });
|
||||
const uris = await this.pickResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems });
|
||||
const uri = uris?.[0];
|
||||
if (uri) {
|
||||
return this.hostService.openWindow([{ folderUri: uri }], { forceNewWindow: options.forceNewWindow, remoteAuthority: options.remoteAuthority });
|
||||
}
|
||||
@@ -282,7 +285,8 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
|
||||
const filters: FileFilter[] = [{ name: nls.localize('filterName.workspace', 'Workspace'), extensions: [WORKSPACE_EXTENSION] }];
|
||||
const availableFileSystems = this.addFileSchemaIfNeeded(schema, true);
|
||||
|
||||
const uri = await this.pickResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems });
|
||||
const uris = await this.pickResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems });
|
||||
const uri = uris?.[0];
|
||||
if (uri) {
|
||||
return this.hostService.openWindow([{ workspaceUri: uri }], { forceNewWindow: options.forceNewWindow, remoteAuthority: options.remoteAuthority });
|
||||
}
|
||||
@@ -316,16 +320,14 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
|
||||
options.availableFileSystems = this.addFileSchemaIfNeeded(schema, options.canSelectFolders);
|
||||
}
|
||||
|
||||
const uri = await this.pickResource(options);
|
||||
|
||||
return uri ? [uri] : undefined;
|
||||
return this.pickResource(options);
|
||||
}
|
||||
|
||||
protected getSimpleFileDialog(): ISimpleFileDialog {
|
||||
return this.instantiationService.createInstance(SimpleFileDialog);
|
||||
}
|
||||
|
||||
private pickResource(options: IOpenDialogOptions): Promise<URI | undefined> {
|
||||
private pickResource(options: IOpenDialogOptions): Promise<URI[] | undefined> {
|
||||
return this.getSimpleFileDialog().showOpenDialog(options);
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ enum UpdateResult {
|
||||
export const RemoteFileDialogContext = new RawContextKey<boolean>('remoteFileDialogVisible', false);
|
||||
|
||||
export interface ISimpleFileDialog extends IDisposable {
|
||||
showOpenDialog(options: IOpenDialogOptions): Promise<URI | undefined>;
|
||||
showOpenDialog(options: IOpenDialogOptions): Promise<URI[] | undefined>;
|
||||
showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined>;
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ export class SimpleFileDialog extends Disposable implements ISimpleFileDialog {
|
||||
return this.filePickBox.busy;
|
||||
}
|
||||
|
||||
public async showOpenDialog(options: IOpenDialogOptions = {}): Promise<URI | undefined> {
|
||||
public async showOpenDialog(options: IOpenDialogOptions = {}): Promise<URI[] | undefined> {
|
||||
this.scheme = this.getScheme(options.availableFileSystems, options.defaultUri);
|
||||
this.userHome = await this.getUserHome();
|
||||
this.trueHome = await this.getUserHome(true);
|
||||
@@ -198,7 +198,11 @@ export class SimpleFileDialog extends Disposable implements ISimpleFileDialog {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
this.options = newOptions;
|
||||
return this.pickResource();
|
||||
const result = await this.pickResource();
|
||||
if (Array.isArray(result)) {
|
||||
return result;
|
||||
}
|
||||
return result ? [result] : undefined;
|
||||
}
|
||||
|
||||
public async showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined> {
|
||||
@@ -215,8 +219,8 @@ export class SimpleFileDialog extends Disposable implements ISimpleFileDialog {
|
||||
this.options.canSelectFiles = true;
|
||||
|
||||
return new Promise<URI | undefined>((resolve) => {
|
||||
this.pickResource(true).then(folderUri => {
|
||||
resolve(folderUri);
|
||||
this.pickResource(true).then(result => {
|
||||
resolve(Array.isArray(result) ? result[0] : result);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -281,7 +285,14 @@ export class SimpleFileDialog extends Disposable implements ISimpleFileDialog {
|
||||
: this.fileDialogService.preferredHome(this.scheme);
|
||||
}
|
||||
|
||||
private async pickResource(isSave: boolean = false): Promise<URI | undefined> {
|
||||
private normalizeUri(uri: URI): URI {
|
||||
uri = resources.addTrailingPathSeparator(uri, this.separator); // Ensures that c: is c:/ since this comes from user input and can be incorrect.
|
||||
// To be consistent, we should never have a trailing path separator on directories (or anything else). Will not remove from c:/.
|
||||
uri = resources.removeTrailingPathSeparator(uri);
|
||||
return uri;
|
||||
}
|
||||
|
||||
private async pickResource(isSave: boolean = false): Promise<URI[] | URI | undefined> {
|
||||
this.allowFolderSelection = !!this.options.canSelectFolders;
|
||||
this.allowFileSelection = !!this.options.canSelectFiles;
|
||||
this.separator = this.labelService.getSeparator(this.scheme, this.remoteAuthority);
|
||||
@@ -302,7 +313,7 @@ export class SimpleFileDialog extends Disposable implements ISimpleFileDialog {
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise<URI | undefined>((resolve) => {
|
||||
return new Promise<URI[] | URI | undefined>((resolve) => {
|
||||
this.filePickBox = this._register(this.quickInputService.createQuickPick<FileQuickPickItem>());
|
||||
this.busy = true;
|
||||
this.filePickBox.matchOnLabel = false;
|
||||
@@ -345,13 +356,15 @@ export class SimpleFileDialog extends Disposable implements ISimpleFileDialog {
|
||||
this.filePickBox.value = this.pathFromUri(this.currentFolder, true);
|
||||
this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length];
|
||||
|
||||
const doResolve = (uri: URI | undefined) => {
|
||||
if (uri) {
|
||||
uri = resources.addTrailingPathSeparator(uri, this.separator); // Ensures that c: is c:/ since this comes from user input and can be incorrect.
|
||||
// To be consistent, we should never have a trailing path separator on directories (or anything else). Will not remove from c:/.
|
||||
uri = resources.removeTrailingPathSeparator(uri);
|
||||
const doResolve = (uriOrUris: URI | URI[] | undefined) => {
|
||||
if (uriOrUris) {
|
||||
if (Array.isArray(uriOrUris)) {
|
||||
uriOrUris = uriOrUris.map(uri => this.normalizeUri(uri));
|
||||
} else {
|
||||
uriOrUris = this.normalizeUri(uriOrUris);
|
||||
}
|
||||
}
|
||||
resolve(uri);
|
||||
resolve(uriOrUris);
|
||||
this.contextKey.set(false);
|
||||
this.dispose();
|
||||
};
|
||||
@@ -373,7 +386,7 @@ export class SimpleFileDialog extends Disposable implements ISimpleFileDialog {
|
||||
});
|
||||
} else {
|
||||
return this.fileDialogService.showOpenDialog(this.options).then(result => {
|
||||
doResolve(result ? result[0] : undefined);
|
||||
doResolve(result);
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -89,10 +89,10 @@ suite('FileDialogService', function () {
|
||||
|
||||
test('Local - open/save workspaces availableFilesystems', async function () {
|
||||
class TestSimpleFileDialog implements ISimpleFileDialog {
|
||||
async showOpenDialog(options: IOpenDialogOptions): Promise<URI | undefined> {
|
||||
async showOpenDialog(options: IOpenDialogOptions): Promise<URI[] | undefined> {
|
||||
assert.strictEqual(options.availableFileSystems?.length, 1);
|
||||
assert.strictEqual(options.availableFileSystems[0], Schemas.file);
|
||||
return testFile;
|
||||
return [testFile];
|
||||
}
|
||||
async showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined> {
|
||||
assert.strictEqual(options.availableFileSystems?.length, 1);
|
||||
@@ -111,10 +111,10 @@ suite('FileDialogService', function () {
|
||||
|
||||
test('Virtual - open/save workspaces availableFilesystems', async function () {
|
||||
class TestSimpleFileDialog {
|
||||
async showOpenDialog(options: IOpenDialogOptions): Promise<URI | undefined> {
|
||||
async showOpenDialog(options: IOpenDialogOptions): Promise<URI[] | undefined> {
|
||||
assert.strictEqual(options.availableFileSystems?.length, 1);
|
||||
assert.strictEqual(options.availableFileSystems[0], Schemas.file);
|
||||
return testFile;
|
||||
return [testFile];
|
||||
}
|
||||
async showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined> {
|
||||
assert.strictEqual(options.availableFileSystems?.length, 1);
|
||||
@@ -137,11 +137,11 @@ suite('FileDialogService', function () {
|
||||
|
||||
test('Remote - open/save workspaces availableFilesystems', async function () {
|
||||
class TestSimpleFileDialog implements ISimpleFileDialog {
|
||||
async showOpenDialog(options: IOpenDialogOptions): Promise<URI | undefined> {
|
||||
async showOpenDialog(options: IOpenDialogOptions): Promise<URI[] | undefined> {
|
||||
assert.strictEqual(options.availableFileSystems?.length, 2);
|
||||
assert.strictEqual(options.availableFileSystems[0], Schemas.vscodeRemote);
|
||||
assert.strictEqual(options.availableFileSystems[1], Schemas.file);
|
||||
return testFile;
|
||||
return [testFile];
|
||||
}
|
||||
async showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined> {
|
||||
assert.strictEqual(options.availableFileSystems?.length, 2);
|
||||
@@ -170,8 +170,8 @@ suite('FileDialogService', function () {
|
||||
|
||||
test('Remote - filters default files/folders to RA (#195938)', async function () {
|
||||
class TestSimpleFileDialog implements ISimpleFileDialog {
|
||||
async showOpenDialog(): Promise<URI | undefined> {
|
||||
return testFile;
|
||||
async showOpenDialog(): Promise<URI[] | undefined> {
|
||||
return [testFile];
|
||||
}
|
||||
async showSaveDialog(): Promise<URI | undefined> {
|
||||
return testFile;
|
||||
|
||||
Reference in New Issue
Block a user