diff --git a/src/sql/platform/query/common/query.ts b/src/sql/platform/query/common/query.ts index 5d82936978..73f6fb8891 100644 --- a/src/sql/platform/query/common/query.ts +++ b/src/sql/platform/query/common/query.ts @@ -32,6 +32,7 @@ export interface IQueryEditorConfiguration { readonly inMemoryDataProcessingThreshold: number; readonly openAfterSave: boolean; readonly showActionBar: boolean; + readonly showCopyCompletedNotification: boolean; readonly preferProvidersCopyHandler: boolean; readonly promptForLargeRowSelection: boolean; }, diff --git a/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts b/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts index a80251876b..5d1b14519e 100644 --- a/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts @@ -413,7 +413,7 @@ export class DataResourceDataProvider implements IGridDataProvider { private async copyResultsAsync(selection: Slick.Range[], includeHeaders?: boolean, tableView?: IDisposableDataProvider): Promise { try { - await copySelectionToClipboard(this._clipboardService, this._notificationService, this, selection, includeHeaders, tableView); + await copySelectionToClipboard(this._clipboardService, this._notificationService, this._configurationService, this, selection, includeHeaders, tableView); } catch (error) { this._notificationService.error(localize('copyFailed', "Copy failed with error: {0}", getErrorMessage(error))); } diff --git a/src/sql/workbench/contrib/query/browser/query.contribution.ts b/src/sql/workbench/contrib/query/browser/query.contribution.ts index f41ff0faf0..92b4a552d1 100644 --- a/src/sql/workbench/contrib/query/browser/query.contribution.ts +++ b/src/sql/workbench/contrib/query/browser/query.contribution.ts @@ -303,6 +303,11 @@ const queryEditorConfiguration: IConfigurationNode = { 'description': localize('queryEditor.results.copyRemoveNewLine', "Configuration options for copying multi-line results from the Results View"), 'default': true }, + 'queryEditor.results.showCopyCompletedNotification': { + 'type': 'boolean', + 'description': localize('queryEditor.results.showCopyCompletedNotification', "Whether to show notifications when a results grid copy operation is completed."), + 'default': true + }, 'queryEditor.results.skipNewLineAfterTrailingLineBreak': { 'type': 'boolean', 'description': localize('queryEditor.results.skipNewLineAfterTrailingLineBreak', "Whether to skip adding a line break between rows when copying results if the previous row already has a trailing line break. The default value is false."), diff --git a/src/sql/workbench/services/query/common/gridDataProvider.ts b/src/sql/workbench/services/query/common/gridDataProvider.ts index c9100fb057..f014dcde7d 100644 --- a/src/sql/workbench/services/query/common/gridDataProvider.ts +++ b/src/sql/workbench/services/query/common/gridDataProvider.ts @@ -13,6 +13,8 @@ import { toAction } from 'vs/base/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { GridRange } from 'sql/base/common/gridRange'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IQueryEditorConfiguration } from 'sql/platform/query/common/query'; export interface IGridDataProvider { @@ -55,9 +57,10 @@ export interface IGridDataProvider { serializeResults(format: SaveFormat, selection: Slick.Range[]): Thenable; } -export async function executeCopyWithNotification(notificationService: INotificationService, selections: Slick.Range[], copyHandler: (notification: INotificationHandle, rowCount: number) => Promise, cancellationTokenSource?: CancellationTokenSource): Promise { +export async function executeCopyWithNotification(notificationService: INotificationService, configurationService: IConfigurationService, selections: Slick.Range[], copyHandler: (notification: INotificationHandle, rowCount: number) => Promise, cancellationTokenSource?: CancellationTokenSource): Promise { const rowRanges = GridRange.getUniqueRows(GridRange.fromSlickRanges(selections)); const rowCount = rowRanges.map(range => range.end - range.start + 1).reduce((p, c) => p + c); + const showCopyCompleteNotifications = configurationService.getValue('queryEditor').results.showCopyCompletedNotification; const notificationHandle = notificationService.notify({ message: nls.localize('gridDataProvider.copying', "Copying..."), severity: Severity.Info, @@ -80,15 +83,30 @@ export async function executeCopyWithNotification(notificationService: INotifica await copyHandler(notificationHandle, rowCount); if (cancellationTokenSource === undefined || !cancellationTokenSource.token.isCancellationRequested) { notificationHandle.progress.done(); - notificationHandle.updateActions({ - primary: [ - toAction({ - id: 'closeCopyResultsNotification', - label: nls.localize('gridDataProvider.closeNotification', "Close"), - run: () => { notificationHandle.close(); } - })] - }); - notificationHandle.updateMessage(nls.localize('gridDataProvider.copyResultsCompleted', "Selected data has been copied to the clipboard. Row count: {0}.", rowCount)); + if (showCopyCompleteNotifications) { + notificationHandle.updateActions({ + primary: [ + toAction({ + id: 'closeCopyResultsNotification', + label: nls.localize('gridDataProvider.closeNotification', 'Close'), + run: () => { notificationHandle.close(); } + }), + toAction({ + id: 'disableCopyNotification', + label: nls.localize('gridDataProvider.disableCopyNotification', `Don't show again`), + run: () => { + updateConfigTurnOffCopyNotifications(configurationService); + notificationService.info(nls.localize('gridDataProvider.turnOnCopyNotificationsMessage', + 'Copy completed notifications are now disabled. To re-enable, modify the setting: queryEditor.results.showCopyCompletedNotification')) + } + })] + }); + notificationHandle.updateMessage(nls.localize('gridDataProvider.copyResultsCompleted', "Selected data has been copied to the clipboard. Row count: {0}.", rowCount)); + // Auto-close notification after 3 seconds. + setTimeout(() => notificationHandle.close(), 3000); + } else { + notificationHandle.close(); + } } } catch (err) { @@ -97,9 +115,10 @@ export async function executeCopyWithNotification(notificationService: INotifica } } -export async function copySelectionToClipboard(clipboardService: IClipboardService, notificationService: INotificationService, provider: IGridDataProvider, selections: Slick.Range[], includeHeaders?: boolean, tableView?: IDisposableDataProvider): Promise { +export async function copySelectionToClipboard(clipboardService: IClipboardService, notificationService: INotificationService, configurationService: IConfigurationService, + provider: IGridDataProvider, selections: Slick.Range[], includeHeaders?: boolean, tableView?: IDisposableDataProvider): Promise { const cancellationTokenSource = new CancellationTokenSource() - await executeCopyWithNotification(notificationService, selections, async (notificationHandle, rowCount) => { + await executeCopyWithNotification(notificationService, configurationService, selections, async (notificationHandle, rowCount) => { const eol = provider.getEolString(); const valueSeparator = '\t'; const shouldRemoveNewLines = provider.shouldRemoveNewLines(); @@ -214,3 +233,10 @@ function removeNewLines(inputString: string): string { let outputString: string = inputString.replace(/(\r\n|\n|\r)/gm, ' '); return outputString; } + +/** + * Disables data copy configuration setting. + */ +function updateConfigTurnOffCopyNotifications(configurationService: IConfigurationService) { + configurationService.updateValue('queryEditor.results.showCopyCompletedNotification', false); +} diff --git a/src/sql/workbench/services/query/common/queryRunner.ts b/src/sql/workbench/services/query/common/queryRunner.ts index eb5dce8193..7fa7fbce54 100644 --- a/src/sql/workbench/services/query/common/queryRunner.ts +++ b/src/sql/workbench/services/query/common/queryRunner.ts @@ -581,7 +581,7 @@ export class QueryGridDataProvider implements IGridDataProvider { if (preferProvidersCopyHandler && providerSupportCopyResults && (tableView === undefined || !tableView.isDataInMemory)) { await this.handleCopyRequestByProvider(selections, includeHeaders); } else { - await copySelectionToClipboard(this._clipboardService, this._notificationService, this, selections, includeHeaders, tableView); + await copySelectionToClipboard(this._clipboardService, this._notificationService, this._configurationService, this, selections, includeHeaders, tableView); } } catch (error) { this._notificationService.error(nls.localize('copyFailed', "Copy failed with error: {0}", getErrorMessage(error))); @@ -589,7 +589,7 @@ export class QueryGridDataProvider implements IGridDataProvider { } private async handleCopyRequestByProvider(selections: Slick.Range[], includeHeaders?: boolean): Promise { - executeCopyWithNotification(this._notificationService, selections, async () => { + executeCopyWithNotification(this._notificationService, this._configurationService, selections, async () => { await this.queryRunner.copyResults(selections, this.batchId, this.resultSetId, this.shouldIncludeHeaders(includeHeaders)); }); }