mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-13 17:22:15 -05:00
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:
@@ -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
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface IQueryEditorConfiguration {
|
||||
readonly inMemoryDataProcessingThreshold: number;
|
||||
readonly openAfterSave: boolean;
|
||||
readonly showActionBar: boolean;
|
||||
readonly preferProvidersCopyHandler: boolean;
|
||||
},
|
||||
readonly messages: {
|
||||
readonly showBatchTime: boolean;
|
||||
|
||||
@@ -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));
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) })));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user