enable provider/extension side copy to clipboard support (#23363)

* improve copy data experience

* add copy result handler

* refactoring

* updates

* add thirdparty notice for TextCopy nuget package

* add await

* comments
This commit is contained in:
Alan Ren
2023-06-09 14:24:45 -07:00
committed by GitHub
parent 80b733ebe0
commit 58082402aa
24 changed files with 217 additions and 104 deletions

View File

@@ -67,6 +67,7 @@ sqltoolsservice: https://github.com/Microsoft/sqltoolsservice
svg.js: https://github.com/svgdotjs/svg.js
systemjs: https://github.com/systemjs/systemjs
temp-write: https://github.com/sindresorhus/temp-write
textcopy: https://github.com/CopyText/TextCopy
turndown: https://github.com/domchristie/turndown
turndown-plugin-gfm: https://github.com/domchristie/turndown-plugin-gfm
underscore: https://github.com/jashkenas/underscore
@@ -3351,3 +3352,30 @@ SOFTWARE.
-------------------------------END OF THIRD-PARTY NOTICES-------------------------------------------
=========================================
END OF Microsoft.ProgramSynthesis.Detection NOTICES AND INFORMATION
%% TextCopy NOTICES AND INFORMATION BEGIN HERE
=========================================
MIT License
Copyright (c) 2018 Simon Cropp
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=========================================
END OF TextCopy NOTICES AND INFORMATION

View File

@@ -209,7 +209,7 @@
"update-grammar": "node ../../build/npm/update-grammar.js Microsoft/vscode-azuremonitor ./syntaxes/azuremonitor.tmLanguage"
},
"dependencies": {
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#1.3.4",
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#1.3.5",
"figures": "^2.0.0",
"find-remove": "1.2.1",
"@microsoft/ads-service-downloader": "^1.2.1",

View File

@@ -75,9 +75,9 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#1.3.4":
version "1.3.4"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/fed4e05caadd89e1f635cc247b82a96a10bc837d"
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#1.3.5":
version "1.3.5"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/23e1f6ea61f10a3b52d4be6f786685a55999fd9a"
dependencies:
vscode-languageclient "5.2.1"

View File

@@ -107,7 +107,7 @@
"dependencies": {
"@microsoft/ads-extension-telemetry": "^3.0.1",
"@microsoft/ads-service-downloader": "^1.2.1",
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#1.3.4",
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#1.3.5",
"vscode-nls": "^5.2.0"
},
"devDependencies": {

View File

@@ -489,9 +489,9 @@ crypt@0.0.2:
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#1.3.4":
version "1.3.4"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/fed4e05caadd89e1f635cc247b82a96a10bc837d"
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#1.3.5":
version "1.3.5"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/23e1f6ea61f10a3b52d4be6f786685a55999fd9a"
dependencies:
vscode-languageclient "5.2.1"

View File

@@ -77,7 +77,7 @@
}
},
"dependencies": {
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#1.3.4",
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#1.3.5",
"htmlparser2": "^3.10.1",
"@microsoft/ads-service-downloader": "^1.2.1",
"@microsoft/ads-extension-telemetry": "^3.0.1",

View File

@@ -486,9 +486,9 @@ crypt@~0.0.1:
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#1.3.4":
version "1.3.4"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/fed4e05caadd89e1f635cc247b82a96a10bc837d"
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#1.3.5":
version "1.3.5"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/23e1f6ea61f10a3b52d4be6f786685a55999fd9a"
dependencies:
vscode-languageclient "5.2.1"

View File

@@ -427,7 +427,7 @@
}
},
"dependencies": {
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#1.3.4",
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#1.3.5",
"figures": "^2.0.0",
"find-remove": "1.2.1",
"@microsoft/ads-service-downloader": "^1.2.1",

View File

@@ -124,9 +124,9 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#1.3.4":
version "1.3.4"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/fed4e05caadd89e1f635cc247b82a96a10bc837d"
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#1.3.5":
version "1.3.5"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/23e1f6ea61f10a3b52d4be6f786685a55999fd9a"
dependencies:
vscode-languageclient "5.2.1"

View File

@@ -1,6 +1,6 @@
{
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
"version": "4.8.0.17",
"version": "4.8.0.20",
"downloadFileNames": {
"Windows_86": "win-x86-net7.0.zip",
"Windows_64": "win-x64-net7.0.zip",

View File

@@ -806,6 +806,7 @@
"providerId": "MSSQL",
"displayName": "%mssql.provider.displayName%",
"isExecutionPlanProvider": true,
"supportCopyResultsToClipboard": true,
"azureResource": "Sql",
"supportedExecutionPlanFileExtensions": [
"sqlplan"
@@ -1458,7 +1459,7 @@
"dependencies": {
"@microsoft/ads-extension-telemetry": "^3.0.1",
"@microsoft/ads-service-downloader": "^1.2.1",
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#1.3.4",
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#1.3.5",
"find-remove": "1.2.1",
"vscode-languageclient": "5.2.1",
"vscode-nls": "^4.0.0"

View File

@@ -425,9 +425,9 @@ crypt@~0.0.1:
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#1.3.4":
version "1.3.4"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/fed4e05caadd89e1f635cc247b82a96a10bc837d"
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#1.3.5":
version "1.3.5"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/23e1f6ea61f10a3b52d4be6f786685a55999fd9a"
dependencies:
vscode-languageclient "5.2.1"

View File

@@ -162,7 +162,7 @@
]
},
"dependencies": {
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#1.3.4",
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#1.3.5",
"@microsoft/ads-service-downloader": "^1.2.1",
"@microsoft/ads-extension-telemetry": "^3.0.1",
"uuid": "^8.3.2",

View File

@@ -62,9 +62,9 @@ chownr@^2.0.0:
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#1.3.4":
version "1.3.4"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/fed4e05caadd89e1f635cc247b82a96a10bc837d"
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#1.3.5":
version "1.3.5"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/23e1f6ea61f10a3b52d4be6f786685a55999fd9a"
dependencies:
vscode-languageclient "5.2.1"

View File

@@ -108,6 +108,13 @@ export interface ConnectionProviderProperties {
* Connection string options for the connection provider
*/
connectionStringOptions?: ConnectionStringOptions;
/**
* Indicates whether the provider support copy results to clipboard. Default value is false.
* If true, the copy results to clipboard will be delegated to the provider to avoid passing large amount of data using the RPC channel.
* Otherwise ADS will handle the copy request on the UI side.
*/
supportCopyResultsToClipboard?: boolean;
}
export interface ProviderFeatures {

View File

@@ -31,6 +31,7 @@ export interface IQueryEditorConfiguration {
readonly inMemoryDataProcessingThreshold: number;
readonly openAfterSave: boolean;
readonly showActionBar: boolean;
readonly preferProvidersCopyHandler: boolean;
},
readonly messages: {
readonly showBatchTime: boolean;

View File

@@ -159,6 +159,9 @@ export class MainThreadDataProtocol extends Disposable implements MainThreadData
return Promise.resolve(self._serializationService.saveAs(requestParams.resultFormat, requestParams.filePath, undefined, true));
}
},
copyResults(requestParams: azdata.CopyResultsRequestParams): Promise<void> {
return Promise.resolve(self._proxy.$copyResults(handle, requestParams));
},
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Promise<void> {
return Promise.resolve(self._proxy.$initializeEdit(handle, ownerUri, schemaName, objectName, objectType, rowLimit, queryString));
},

View File

@@ -414,6 +414,15 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
return this._resolveProvider<azdata.QueryProvider>(handle).saveResults(requestParams);
}
override $copyResults(handle: number, requestParams: azdata.CopyResultsRequestParams): Thenable<void> {
const provider = this._resolveProvider<azdata.QueryProvider>(handle);
if (provider.copyResults) {
return provider.copyResults(requestParams);
} else {
throw new Error(`copyResults() is not implemented by the provider`);
}
}
// Edit Data handlers
override $commitEdit(handle: number, ownerUri: string): Thenable<void> {
return this._resolveProvider<azdata.QueryProvider>(handle).commitEdit(ownerUri);

View File

@@ -251,6 +251,11 @@ export abstract class ExtHostDataProtocolShape {
*/
$saveResults(handle: number, requestParams: azdata.SaveResultsRequestParams): Thenable<azdata.SaveResultRequestResult> { throw ni(); }
/**
* Copies the selected data to clipboard.
*/
$copyResults(handle: number, requestParams: azdata.CopyResultsRequestParams): Thenable<void> { throw ni(); }
/**
* Commits all pending edits in an edit session
*/

View File

@@ -404,6 +404,11 @@ const queryEditorConfiguration: IConfigurationNode = {
'description': localize('queryEditor.results.copyRemoveNewLine', "Configuration options for copying multi-line results from the Results View"),
'default': true
},
'queryEditor.results.preferProvidersCopyHandler': {
'type': 'boolean',
'description': localize('queryEditor.results.preferProvidersCopyHandler', "Whether the copy result request should be handled by the query provider when it is supported. The default value is true, set this to false to force all copy handling to be done by Azure Data Studio."),
'default': true
},
'queryEditor.results.inMemoryDataProcessingThreshold': {
'type': 'number',
'default': 5000,

View File

@@ -7,7 +7,7 @@ import * as types from 'vs/base/common/types';
import { SaveFormat } from 'sql/workbench/services/query/common/resultSerializer';
import { ICellValue, ResultSetSubset } from 'sql/workbench/services/query/common/query';
import { IDisposableDataProvider } from 'sql/base/common/dataProvider';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { INotificationHandle, INotificationService, Severity } from 'vs/platform/notification/common/notification';
import * as nls from 'vs/nls';
import { toAction } from 'vs/base/common/actions';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
@@ -79,7 +79,53 @@ function mergeRanges(ranges: Range[]): Range[] {
return mergedRanges;
}
export async function executeCopyWithNotification(notificationService: INotificationService, selections: Slick.Range[], isCancelable: boolean, copyHandler: (notification: INotificationHandle, rowCount: number) => Promise<void>, onCanceled?: () => void): Promise<void> {
const rowRanges: Range[] = mergeRanges(selections.map(selection => { return { start: selection.fromRow, end: selection.toRow }; }));
const rowCount = rowRanges.map(range => range.end - range.start + 1).reduce((p, c) => p + c);
let isCanceled = false;
const notificationHandle = notificationService.notify({
message: nls.localize('gridDataProvider.copying', "Copying..."),
severity: Severity.Info,
progress: {
infinite: true
},
actions: {
primary: isCancelable ? [
toAction({
id: 'cancelCopyResults',
label: nls.localize('gridDataProvider.cancelCopyResults', "Cancel"),
run: () => {
isCanceled = true;
onCanceled!();
notificationHandle.close();
}
})] : []
}
});
try {
await copyHandler(notificationHandle, rowCount);
if (!isCanceled) {
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));
}
}
catch (err) {
notificationHandle.close();
throw err;
}
}
export async function copySelectionToClipboard(clipboardService: IClipboardService, notificationService: INotificationService, provider: IGridDataProvider, selections: Slick.Range[], includeHeaders?: boolean, tableView?: IDisposableDataProvider<Slick.SlickData>): Promise<void> {
let isCanceled = false;
await executeCopyWithNotification(notificationService, selections, true, async (notificationHandle, rowCount) => {
const batchSize = 100;
const eol = provider.getEolString();
const valueSeparator = '\t';
@@ -89,34 +135,10 @@ export async function copySelectionToClipboard(clipboardService: IClipboardServi
const columnRanges: Range[] = mergeRanges(selections.map(selection => { return { start: selection.fromCell, end: selection.toCell }; }));
const rowRanges: Range[] = mergeRanges(selections.map(selection => { return { start: selection.fromRow, end: selection.toRow }; }));
const totalRows = rowRanges.map(range => range.end - range.start + 1).reduce((p, c) => p + c);
let processedRows = 0;
const getMessageText = (): string => {
return nls.localize('gridDataProvider.loadingRowsInProgress', "Loading the rows to be copied ({0}/{1})...", processedRows, totalRows);
return nls.localize('gridDataProvider.loadingRowsInProgress', "Loading the rows to be copied ({0}/{1})...", processedRows, rowCount);
};
let isCanceled = false;
const notificationHandle = notificationService.notify({
message: getMessageText(),
severity: Severity.Info,
progress: {
infinite: true
},
actions: {
primary: [
toAction({
id: 'cancelCopyResults',
label: nls.localize('gridDataProvider.cancelCopyResults', "Cancel"),
run: () => {
isCanceled = true;
notificationHandle.close();
}
})]
}
});
let resultString = '';
if (includeHeaders) {
const headers: string[] = [];
@@ -156,18 +178,11 @@ export async function copySelectionToClipboard(clipboardService: IClipboardServi
}
if (!isCanceled) {
resultString += batchResult.join(eol);
notificationHandle.progress.done();
notificationHandle.updateActions({
primary: [
toAction({
id: 'closeCopyResultsNotification',
label: nls.localize('gridDataProvider.closeNotification', "Close"),
run: () => { notificationHandle.close(); }
})]
});
await clipboardService.writeText(resultString);
notificationHandle.updateMessage(nls.localize('gridDataProvider.copyResultsCompleted', "Selected data has been copied to the clipboard. Row count: {0}.", totalRows));
}
}, () => {
isCanceled = true;
});
}
function getStringValueForRowSet(rows: ICellValue[][], columnRanges: Range[], selections: Slick.Range[], rowSetStartIndex: number, eol: string, valueSeparator: string, shouldRemoveNewLines: boolean): string {

View File

@@ -55,6 +55,7 @@ export interface IQueryManagementService {
changeConnectionUri(newUri: string, oldUri: string): Promise<void>;
saveResults(requestParams: azdata.SaveResultsRequestParams): Promise<azdata.SaveResultRequestResult>;
setQueryExecutionOptions(uri: string, options: azdata.QueryExecutionOptions): Promise<void>;
copyResults(params: azdata.CopyResultsRequestParams): Promise<void>;
// Callbacks
onQueryComplete(result: azdata.QueryExecuteCompleteNotificationResult): void;
@@ -93,6 +94,7 @@ export interface IQueryRequestHandler {
disposeQuery(ownerUri: string): Promise<void>;
connectionUriChanged(newUri: string, oldUri: string): Promise<void>;
saveResults(requestParams: azdata.SaveResultsRequestParams): Promise<azdata.SaveResultRequestResult>;
copyResults(requestParams: azdata.CopyResultsRequestParams): Promise<void>;
setQueryExecutionOptions(ownerUri: string, options: azdata.QueryExecutionOptions): Promise<void>;
// Edit Data actions
@@ -311,6 +313,12 @@ export class QueryManagementService implements IQueryManagementService {
});
}
public copyResults(requestParams: azdata.CopyResultsRequestParams): Promise<void> {
return this._runAction(requestParams.ownerUri, (runner) => {
return runner.copyResults(requestParams);
});
}
public onQueryComplete(result: azdata.QueryExecuteCompleteNotificationResult): void {
this._notify(result.ownerUri, (runner: QueryRunner) => {
runner.handleQueryComplete(result.batchSummaries.map(s => ({ ...s, range: selectionDataToRange(s.selection) })));

View File

@@ -21,7 +21,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { URI } from 'vs/base/common/uri';
import * as perf from 'vs/base/common/performance';
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
import { IGridDataProvider, copySelectionToClipboard, getTableHeaderString } from 'sql/workbench/services/query/common/gridDataProvider';
import { IGridDataProvider, copySelectionToClipboard, executeCopyWithNotification, getTableHeaderString } from 'sql/workbench/services/query/common/gridDataProvider';
import { getErrorMessage } from 'vs/base/common/errors';
import { ILogService } from 'vs/platform/log/common/log';
import { IRange, Range } from 'vs/editor/common/core/range';
@@ -29,6 +29,7 @@ import { BatchSummary, IQueryMessage, ResultSetSummary, QueryExecuteSubsetParams
import { IQueryEditorConfiguration } from 'sql/platform/query/common/query';
import { IDisposableDataProvider } from 'sql/base/common/dataProvider';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
/*
* Query Runner class which handles running a query, reports the results to the content manager,
@@ -460,17 +461,30 @@ export default class QueryRunner extends Disposable {
/**
* Sends a copy request
* @param selection The selection range to copy
* @param selections The selection range to copy
* @param batchId The batch id of the result to copy from
* @param resultId The result id of the result to copy from
* @param removeNewLines Whether to remove line breaks from values.
* @param includeHeaders [Optional]: Should column headers be included in the copy selection
*/
async copyResults(selection: Slick.Range[], batchId: number, resultId: number, includeHeaders?: boolean): Promise<void> {
let provider = this.getGridDataProvider(batchId, resultId);
return provider.copyResults(selection, includeHeaders);
async copyResults(selections: Slick.Range[], batchId: number, resultId: number, removeNewLines: boolean, includeHeaders?: boolean): Promise<void> {
await this.queryManagementService.copyResults({
ownerUri: this.uri,
batchIndex: batchId,
resultSetIndex: resultId,
removeNewLines: removeNewLines,
includeHeaders: includeHeaders,
selections: selections.map(selection => {
return {
fromRow: selection.fromRow,
toRow: selection.toRow,
fromColumn: selection.fromCell,
toColumn: selection.toCell
};
})
});
}
public getColumnHeaders(batchId: number, resultId: number, range: Slick.Range): string[] | undefined {
let headers: string[] | undefined = undefined;
let batchSummary: BatchSummary = this._batchSets[batchId];
@@ -547,7 +561,8 @@ export class QueryGridDataProvider implements IGridDataProvider {
@INotificationService private _notificationService: INotificationService,
@IClipboardService private _clipboardService: IClipboardService,
@IConfigurationService private _configurationService: IConfigurationService,
@ITextResourcePropertiesService private _textResourcePropertiesService: ITextResourcePropertiesService
@ITextResourcePropertiesService private _textResourcePropertiesService: ITextResourcePropertiesService,
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService
) {
}
@@ -559,14 +574,27 @@ export class QueryGridDataProvider implements IGridDataProvider {
return this.copyResultsAsync(selection, includeHeaders, tableView);
}
private async copyResultsAsync(selection: Slick.Range[], includeHeaders?: boolean, tableView?: IDisposableDataProvider<Slick.SlickData>): Promise<void> {
private async copyResultsAsync(selections: Slick.Range[], includeHeaders?: boolean, tableView?: IDisposableDataProvider<Slick.SlickData>): Promise<void> {
try {
await copySelectionToClipboard(this._clipboardService, this._notificationService, this, selection, includeHeaders, tableView);
const providerId = this.queryRunner.getProviderId();
const providerSupportCopyResults = this._capabilitiesService.getCapabilities(providerId).connection.supportCopyResultsToClipboard;
const preferProvidersCopyHandler = this._configurationService.getValue<IQueryEditorConfiguration>('queryEditor').results.preferProvidersCopyHandler;
if (preferProvidersCopyHandler && providerSupportCopyResults && (tableView === undefined || !tableView.isDataInMemory)) {
await this.handleCopyRequestByProvider(selections, includeHeaders);
} else {
await copySelectionToClipboard(this._clipboardService, this._notificationService, this, selections, includeHeaders, tableView);
}
} catch (error) {
this._notificationService.error(nls.localize('copyFailed', "Copy failed with error: {0}", getErrorMessage(error)));
}
}
private async handleCopyRequestByProvider(selections: Slick.Range[], includeHeaders?: boolean): Promise<void> {
executeCopyWithNotification(this._notificationService, selections, false, async () => {
await this.queryRunner.copyResults(selections, this.batchId, this.resultSetId, this.shouldRemoveNewLines(), this.shouldIncludeHeaders(includeHeaders));
});
}
async copyHeaders(selection: Slick.Range[]): Promise<void> {
try {
const results = getTableHeaderString(this, selection);

View File

@@ -62,6 +62,9 @@ export class TestQueryManagementService implements IQueryManagementService {
saveResults(requestParams: azdata.SaveResultsRequestParams): Promise<azdata.SaveResultRequestResult> {
throw new Error('Method not implemented.');
}
copyResults(params: azdata.CopyResultsRequestParams): Promise<void> {
throw new Error('Method not implemented.');
}
setQueryExecutionOptions(uri: string, options: azdata.QueryExecutionOptions): Promise<void> {
throw new Error('Method not implemented.');
}