diff --git a/src/sql/azdata.proposed.d.ts b/src/sql/azdata.proposed.d.ts index a5413097cd..dccc39aeb2 100644 --- a/src/sql/azdata.proposed.d.ts +++ b/src/sql/azdata.proposed.d.ts @@ -2011,6 +2011,27 @@ declare module 'azdata' { */ isPrimary: boolean; } + + export interface FileFilters { + /** + * The label to display in the file filter field next to the list of filters. + */ + label: string; + /** + * The filters to limit what files are visible in the file browser (e.g. '*.sql' for SQL files). + */ + filters: string[]; + } + + /** + * Opens a dialog to select a file path on the specified server's machine. Note: The dialog for just browsing local + * files without any connection is opened via vscode.window.showOpenDialog. + * @param connectionUri The URI of the connection to the target server + * @param targetPath The file path on the server machine to open by default in the dialog + * @param fileFilters The filters used to limit which files are displayed in the file browser + * @returns The path of the file chosen from the dialog, and undefined if the dialog is closed without selecting anything. + */ + export function openServerFileBrowserDialog(connectionUri: string, targetPath: string, fileFilters: FileFilters[]): Thenable; } export interface TableComponent { diff --git a/src/sql/workbench/api/browser/extensionHost.contribution.ts b/src/sql/workbench/api/browser/extensionHost.contribution.ts index 82dffc6b73..bea98229b1 100644 --- a/src/sql/workbench/api/browser/extensionHost.contribution.ts +++ b/src/sql/workbench/api/browser/extensionHost.contribution.ts @@ -24,3 +24,4 @@ import './mainThreadResourceProvider'; import './mainThreadErrorDiagnostics'; import './mainThreadTasks'; import './mainThreadWorkspace'; +import './mainThreadWindow'; diff --git a/src/sql/workbench/api/browser/mainThreadWindow.ts b/src/sql/workbench/api/browser/mainThreadWindow.ts new file mode 100644 index 0000000000..d19c9a4fa3 --- /dev/null +++ b/src/sql/workbench/api/browser/mainThreadWindow.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type * as azdata from 'azdata'; +import { MainThreadWindowShape } from 'sql/workbench/api/common/sqlExtHost.protocol'; +import { IFileBrowserDialogController } from 'sql/workbench/services/fileBrowser/common/fileBrowserDialogController'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { SqlMainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; + +@extHostNamedCustomer(SqlMainContext.MainThreadWindow) +export class MainThreadWindow extends Disposable implements MainThreadWindowShape { + + constructor( + extHostContext: IExtHostContext, + @IFileBrowserDialogController private _fileBrowserDialogService: IFileBrowserDialogController + ) { + super(); + } + + public async $openServerFileBrowserDialog(connectionUri: string, targetPath: string, fileFilters: azdata.window.FileFilters[]): Promise { + let completion = new Promise((resolve, reject) => { + try { + const handleOnClosed = (path: string | undefined) => { + resolve(path); + }; + this._fileBrowserDialogService.showDialog(connectionUri, targetPath, fileFilters, '', true, handleOnClosed); + } catch (error) { + reject(error); + } + }); + return await completion; + } +} diff --git a/src/sql/workbench/api/common/extHostWindow.ts b/src/sql/workbench/api/common/extHostWindow.ts new file mode 100644 index 0000000000..039639ccf1 --- /dev/null +++ b/src/sql/workbench/api/common/extHostWindow.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type * as azdata from 'azdata'; +import { IMainContext } from 'vs/workbench/api/common/extHost.protocol'; + +import { ExtHostWindowShape, MainThreadWindowShape } from 'sql/workbench/api/common/sqlExtHost.protocol'; +import { SqlMainContext } from 'vs/workbench/api/common/extHost.protocol'; + +export class ExtHostWindow implements ExtHostWindowShape { + + private readonly _proxy: MainThreadWindowShape; + + constructor(_mainContext: IMainContext) { + this._proxy = _mainContext.getProxy(SqlMainContext.MainThreadWindow); + } + + $openServerFileBrowserDialog(connectionUri: string, targetPath: string, fileFilters: azdata.window.FileFilters[]): Promise { + return this._proxy.$openServerFileBrowserDialog(connectionUri, targetPath, fileFilters); + } +} diff --git a/src/sql/workbench/api/common/sqlExtHost.api.impl.ts b/src/sql/workbench/api/common/sqlExtHost.api.impl.ts index c8b5bb6c3d..083b516470 100644 --- a/src/sql/workbench/api/common/sqlExtHost.api.impl.ts +++ b/src/sql/workbench/api/common/sqlExtHost.api.impl.ts @@ -42,6 +42,7 @@ import { ExtHostAzureBlob } from 'sql/workbench/api/common/extHostAzureBlob'; import { ExtHostAzureAccount } from 'sql/workbench/api/common/extHostAzureAccount'; import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { AuthenticationType } from 'sql/platform/connection/common/constants'; +import { ExtHostWindow } from 'sql/workbench/api/common/extHostWindow'; export interface IAzdataExtensionApiFactory { (extension: IExtensionDescription): typeof azdata; @@ -102,6 +103,7 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp const extHostNotebook = rpcProtocol.set(SqlExtHostContext.ExtHostNotebook, new ExtHostNotebook(rpcProtocol)); const extHostExtensionManagement = rpcProtocol.set(SqlExtHostContext.ExtHostExtensionManagement, new ExtHostExtensionManagement(rpcProtocol)); const extHostWorkspace = rpcProtocol.set(SqlExtHostContext.ExtHostWorkspace, new ExtHostWorkspace(rpcProtocol)); + const extHostWindow = rpcProtocol.set(SqlExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol)); return { azdata: function (extension: IExtensionDescription): typeof azdata { // namespace: connection @@ -480,6 +482,9 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp MessageLevel: sqlExtHostTypes.MessageLevel, openCustomErrorDialog(options: sqlExtHostTypes.IErrorDialogOptions): Thenable { return extHostModelViewDialog.openCustomErrorDialog(options); + }, + openServerFileBrowserDialog(connectionUri: string, targetPath: string, fileFilters: azdata.window.FileFilters[]): Thenable { + return extHostWindow.$openServerFileBrowserDialog(connectionUri, targetPath, fileFilters); } }; diff --git a/src/sql/workbench/api/common/sqlExtHost.protocol.ts b/src/sql/workbench/api/common/sqlExtHost.protocol.ts index cded567a3e..7bdeee8119 100644 --- a/src/sql/workbench/api/common/sqlExtHost.protocol.ts +++ b/src/sql/workbench/api/common/sqlExtHost.protocol.ts @@ -827,12 +827,20 @@ export interface ExtHostWorkspaceShape { $saveAndEnterWorkspace(workspaceFile: vscode.Uri): Promise; } +export interface ExtHostWindowShape { + $openServerFileBrowserDialog(connectionUri: string, targetPath: string, fileFilters: azdata.window.FileFilters[]): Promise; +} + export interface MainThreadWorkspaceShape { $createAndEnterWorkspace(folder: vscode.Uri, workspaceFile: vscode.Uri): Promise; $enterWorkspace(workspaceFile: vscode.Uri): Promise; $saveAndEnterWorkspace(workspaceFile: vscode.Uri): Promise; } +export interface MainThreadWindowShape { + $openServerFileBrowserDialog(connectionUri: string, targetPath: string, fileFilters: azdata.window.FileFilters[]): Promise; +} + export interface MainThreadBackgroundTaskManagementShape extends IDisposable { $registerTask(taskInfo: azdata.TaskInfo): void; $updateTask(taskProgressInfo: azdata.TaskProgressInfo): void; diff --git a/src/sql/workbench/contrib/backup/browser/backup.component.ts b/src/sql/workbench/contrib/backup/browser/backup.component.ts index 0c1d2cc45e..0cd13a3659 100644 --- a/src/sql/workbench/contrib/backup/browser/backup.component.ts +++ b/src/sql/workbench/contrib/backup/browser/backup.component.ts @@ -766,7 +766,7 @@ export class BackupComponent extends AngularDisposable { } } - private handleFilePathAdded(filepath: string): void { + private handleFilePathAdded(filepath?: string): void { if (filepath && !this.backupPathTypePairs![filepath]) { if ((this.getBackupPathCount() < BackupConstants.maxDevices)) { this.backupPathTypePairs![filepath] = BackupConstants.MediaDeviceType.File; diff --git a/src/sql/workbench/services/fileBrowser/browser/fileBrowserDialogController.ts b/src/sql/workbench/services/fileBrowser/browser/fileBrowserDialogController.ts index 9b01724b47..a5ec17f2e6 100644 --- a/src/sql/workbench/services/fileBrowser/browser/fileBrowserDialogController.ts +++ b/src/sql/workbench/services/fileBrowser/browser/fileBrowserDialogController.ts @@ -25,7 +25,7 @@ export class FileBrowserDialogController implements IFileBrowserDialogController fileFilters: [{ label: string, filters: string[] }], fileValidationServiceType: string, isWide: boolean, - handleOnOk: (path: string) => void + handleOnClosed: (path: string | undefined) => void ): void { if (!this._fileBrowserDialog) { this._fileBrowserDialog = this._instantiationService.createInstance(FileBrowserDialog, localize('filebrowser.selectFile', "Select a file")); @@ -33,7 +33,17 @@ export class FileBrowserDialogController implements IFileBrowserDialogController } this._fileBrowserDialog.setWide(isWide); - this._fileBrowserDialog.onOk((filepath) => handleOnOk(filepath)); + var onOK = this._fileBrowserDialog.onOk((filepath) => handleOnClosed(filepath)); + var onClosed = this._fileBrowserDialog.onClosed((hideReason) => { + if (hideReason !== 'ok') { + handleOnClosed(undefined); + } + onOK.dispose(); + onClosed.dispose(); + this._fileBrowserDialog.dispose(); + this._fileBrowserDialog = undefined; + }); + this._fileBrowserDialog.open(ownerUri, expandPath, fileFilters, fileValidationServiceType); } } diff --git a/src/sql/workbench/services/fileBrowser/common/fileBrowserDialogController.ts b/src/sql/workbench/services/fileBrowser/common/fileBrowserDialogController.ts index 16df3568ae..01d195fa96 100644 --- a/src/sql/workbench/services/fileBrowser/common/fileBrowserDialogController.ts +++ b/src/sql/workbench/services/fileBrowser/common/fileBrowserDialogController.ts @@ -16,5 +16,5 @@ export interface IFileBrowserDialogController { fileFilters: { label: string, filters: string[] }[], fileValidationServiceType: string, isWide: boolean, - handleOnOk: (path: string) => void): void; + handleOnOk: (path: string | undefined) => void): void; } diff --git a/src/sql/workbench/services/restore/browser/restoreDialog.ts b/src/sql/workbench/services/restore/browser/restoreDialog.ts index 7aba1b0837..3c9584c252 100644 --- a/src/sql/workbench/services/restore/browser/restoreDialog.ts +++ b/src/sql/workbench/services/restore/browser/restoreDialog.ts @@ -725,16 +725,18 @@ export class RestoreDialog extends Modal { .then(url => this._urlInputBox!.value = url); } - private onFileBrowsed(filepath: string): void { - const oldFilePath = this._filePathInputBox!.value; - if (strings.isFalsyOrWhitespace(this._filePathInputBox!.value)) { - this._filePathInputBox!.value = filepath; - } else { - this._filePathInputBox!.value = this._filePathInputBox!.value + ', ' + filepath; - } + private onFileBrowsed(filepath?: string): void { + if (filepath) { + const oldFilePath = this._filePathInputBox!.value; + if (strings.isFalsyOrWhitespace(this._filePathInputBox!.value)) { + this._filePathInputBox!.value = filepath; + } else { + this._filePathInputBox!.value = this._filePathInputBox!.value + ', ' + filepath; + } - if (oldFilePath !== this._filePathInputBox!.value) { - this.onFilePathChanged(this._filePathInputBox!.value); + if (oldFilePath !== this._filePathInputBox!.value) { + this.onFilePathChanged(this._filePathInputBox!.value); + } } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index e63150c8f4..33d1992729 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -87,11 +87,13 @@ import { MainThreadModalDialogShape, MainThreadModelViewDialogShape, MainThreadModelViewShape, MainThreadNotebookDocumentsAndEditorsShape, MainThreadObjectExplorerShape, MainThreadQueryEditorShape, MainThreadResourceProviderShape, MainThreadErrorDiagnosticsShape, MainThreadTasksShape, MainThreadNotebookShape as SqlMainThreadNotebookShape, MainThreadWorkspaceShape as SqlMainThreadWorkspaceShape, + MainThreadWindowShape as SqlMainThreadWindowShape, ExtHostAccountManagementShape, ExtHostAzureAccountShape, ExtHostConnectionManagementShape, ExtHostCredentialManagementShape, ExtHostDataProtocolShape, ExtHostObjectExplorerShape, ExtHostResourceProviderShape, ExtHostErrorDiagnosticsShape, ExtHostModalDialogsShape, ExtHostTasksShape, ExtHostBackgroundTaskManagementShape, ExtHostDashboardWebviewsShape, ExtHostModelViewShape, ExtHostModelViewTreeViewsShape, ExtHostDashboardShape, ExtHostModelViewDialogShape, ExtHostQueryEditorShape, ExtHostExtensionManagementShape, ExtHostAzureBlobShape, ExtHostNotebookShape as SqlExtHostNotebookShape, ExtHostWorkspaceShape as SqlExtHostWorkspaceShape, + ExtHostWindowShape as SqlExtHostWindowShape, ExtHostNotebookDocumentsAndEditorsShape as SqlExtHostNotebookDocumentsAndEditorsShape, ExtHostPerfShape, MainThreadPerfShape @@ -2684,7 +2686,8 @@ export const SqlMainContext = { MainThreadExtensionManagement: createProxyIdentifier('MainThreadExtensionManagement'), MainThreadWorkspace: createProxyIdentifier('MainThreadWorkspace'), MainThreadAzureBlob: createProxyIdentifier('MainThreadAzureBlob'), - MainThreadPerf: createProxyIdentifier('MainThreadPerf') + MainThreadPerf: createProxyIdentifier('MainThreadPerf'), + MainThreadWindow: createProxyIdentifier('MainThreadWindow') }; export const SqlExtHostContext = { @@ -2709,6 +2712,7 @@ export const SqlExtHostContext = { ExtHostNotebookDocumentsAndEditors: createProxyIdentifier('ExtHostNotebookDocumentsAndEditors'), ExtHostExtensionManagement: createProxyIdentifier('ExtHostExtensionManagement'), ExtHostWorkspace: createProxyIdentifier('ExtHostWorkspace'), + ExtHostWindow: createProxyIdentifier('ExtHostWindow'), ExtHostAzureBlob: createProxyIdentifier('ExtHostAzureBlob'), ExtHostPerf: createProxyIdentifier('ExtHostPerf') };