Skip to content

Commit

Permalink
Copy clipboard command in ADS (html/plain text supported) (#13527)
Browse files Browse the repository at this point in the history
* draft commit

* few changes

* Changes to copy query with results in plain and html formatting

* undo changes

* undo unintended change

* remove comments

* Addressed comments

* Some clean up

Co-authored-by: Monica Gupta <mogupt@microsoft.com>
  • Loading branch information
2 people authored and kburtram committed Nov 30, 2020
1 parent 71e0228 commit 4a201b1
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 4 deletions.
6 changes: 5 additions & 1 deletion src/sql/platform/clipboard/browser/clipboardService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
import { IClipboardService as vsIClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { ClipboardData, IClipboardService as vsIClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
Expand All @@ -25,6 +25,10 @@ export class BrowserClipboardService implements IClipboardService {
this._notificationService.info(localize('imageCopyingNotSupported', "Copying images is not supported"));
}

write(data: ClipboardData): Promise<void> {
return this._vsClipboardService.write(data);
}

writeText(text: string): Promise<void> {
return this._vsClipboardService.writeText(text);
}
Expand Down
70 changes: 70 additions & 0 deletions src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Action } from 'vs/base/common/actions';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';

import * as azdata from 'azdata';
import { escape } from 'sql/base/common/strings';

import { IQueryManagementService } from 'sql/workbench/services/query/common/queryManagement';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
Expand All @@ -21,6 +22,7 @@ import { EditDataEditor } from 'sql/workbench/contrib/editData/browser/editDataE
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { QueryEditorInput } from 'sql/workbench/common/editor/query/queryEditorInput';
import { ClipboardData, IClipboardService } from 'vs/platform/clipboard/common/clipboardService';

const singleQuote = '\'';

Expand Down Expand Up @@ -131,6 +133,74 @@ export class RunCurrentQueryKeyboardAction extends Action {
}
}

export class CopyQueryWithResultsKeyboardAction extends Action {
public static ID = 'copyQueryWithResultsKeyboardAction';
public static LABEL = nls.localize('copyQueryWithResultsKeyboardAction', "Copy Query With Results");

constructor(
id: string,
label: string,
@IEditorService private _editorService: IEditorService,
@IClipboardService private _clipboardService: IClipboardService,
@IQueryModelService protected readonly queryModelService: IQueryModelService,
) {
super(id, label);
}

public async getFormattedResults(editor): Promise<ClipboardData> {
let queryRunner = this.queryModelService.getQueryRunner(editor.input.uri);
let allResults = '';
let allHtmlResults = '';

if (queryRunner && queryRunner.batchSets.length > 0) {
for (let i = 0; i < queryRunner.batchSets[0].resultSetSummaries.length; i++) {
let resultSummary = queryRunner.batchSets[0].resultSetSummaries[i];
let result = await queryRunner.getQueryRows(0, resultSummary.rowCount, resultSummary.batchId, resultSummary.id);
let tableHeaders = resultSummary.columnInfo.map((col, i) => (col.columnName));
let htmlTableHeaders = resultSummary.columnInfo.map((col, i) => (`<th style="border:solid black 1.0pt; whiteSpace:nowrap">${escape(col.columnName)}</th>`));
let copyString = '\n';
let htmlCopyString = '<tr>';

for (let rowEntry of result.rows) {
for (let colIdx = 0; colIdx < rowEntry.length; colIdx++) {
let value = rowEntry[colIdx].displayValue;
if (value) {
copyString = `${copyString}${value}\t`;
htmlCopyString = `${htmlCopyString}<td style="border:solid black 1.0pt;white-space:nowrap">${escape(value)}</td>`;
}
}
// Removes the tab seperator from the end of a row
copyString = copyString.slice(0, -1 * '\t'.length) + '\n';
htmlCopyString = htmlCopyString + '</tr>';
}

allResults = `${allResults}${tableHeaders.join('\t')}${copyString}\n`;
allHtmlResults = `${allHtmlResults}<div><br/><br/>
<table cellPadding="5" cellSpacing="1" style="border:1;border-color:Black;font-family:Segoe UI;font-size:12px;border-collapse:collapse">
<tr style="background-color:DarkGray">${htmlTableHeaders.join('')}</tr>${htmlCopyString}
</table></div>`;
}
}

return { text: allResults, html: allHtmlResults };
}

public async run(): Promise<void> {
const editor = this._editorService.activeEditorPane;
if (editor instanceof QueryEditor) {
let allResults = await this.getFormattedResults(editor);
let queryText = editor.getAllText();

let data = {
text: `${queryText}\n\n${allResults.text}`,
html: `${escape(queryText).replace(/\r\n|\n|\r/gm, '<br />')}${allResults.html}`
};

await this._clipboardService.write(data);
}
}
}

export class RunCurrentQueryWithActualPlanKeyboardAction extends Action {
public static ID = 'runCurrentQueryWithActualPlanKeyboardAction';
public static LABEL = nls.localize('runCurrentQueryWithActualPlanKeyboardAction', "Run Current Query with Actual Plan");
Expand Down
12 changes: 11 additions & 1 deletion src/sql/workbench/contrib/query/browser/query.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { QueryResultsInput } from 'sql/workbench/common/editor/query/queryResult
import * as queryContext from 'sql/workbench/contrib/query/common/queryContext';
import {
RunQueryKeyboardAction, RunCurrentQueryKeyboardAction, CancelQueryKeyboardAction, RefreshIntellisenseKeyboardAction, ToggleQueryResultsKeyboardAction,
RunQueryShortcutAction, RunCurrentQueryWithActualPlanKeyboardAction, FocusOnCurrentQueryKeyboardAction, ParseSyntaxAction
RunQueryShortcutAction, RunCurrentQueryWithActualPlanKeyboardAction, CopyQueryWithResultsKeyboardAction, FocusOnCurrentQueryKeyboardAction, ParseSyntaxAction
} from 'sql/workbench/contrib/query/browser/keyboardQueryActions';
import * as gridActions from 'sql/workbench/contrib/editData/browser/gridActions';
import * as gridCommands from 'sql/workbench/contrib/editData/browser/gridCommands';
Expand Down Expand Up @@ -139,6 +139,16 @@ actionRegistry.registerWorkbenchAction(
RunCurrentQueryWithActualPlanKeyboardAction.LABEL
);

actionRegistry.registerWorkbenchAction(
SyncActionDescriptor.create(
CopyQueryWithResultsKeyboardAction,
CopyQueryWithResultsKeyboardAction.ID,
CopyQueryWithResultsKeyboardAction.LABEL,
{ primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_C }
),
CopyQueryWithResultsKeyboardAction.LABEL
);

actionRegistry.registerWorkbenchAction(
SyncActionDescriptor.create(
CancelQueryKeyboardAction,
Expand Down
7 changes: 6 additions & 1 deletion src/vs/platform/clipboard/browser/clipboardService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { ClipboardData, IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; // {{SQL CARBON EDIT}}
import { URI } from 'vs/base/common/uri';
import { $ } from 'vs/base/browser/dom';

Expand All @@ -13,6 +13,11 @@ export class BrowserClipboardService implements IClipboardService {

private readonly mapTextToType = new Map<string, string>(); // unsupported in web (only in-memory)

// {{SQL CARBON EDIT}}
async write(data: ClipboardData, type?: string): Promise<void> {
throw new Error('Not Implemented');
}

async writeText(text: string, type?: string): Promise<void> {

// With type: only in-memory is supported
Expand Down
12 changes: 12 additions & 0 deletions src/vs/platform/clipboard/common/clipboardService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,21 @@ import { URI } from 'vs/base/common/uri';

export const IClipboardService = createDecorator<IClipboardService>('clipboardService');

// Added type https://www.electronjs.org/docs/api/clipboard#clipboardwritedata-type
export interface ClipboardData { // {{SQL CARBON EDIT}}
text?: string;
html?: string;
rtf?: string;
bookmark?: string;
}

export interface IClipboardService {

readonly _serviceBrand: undefined;
/**
* Writes data to the system clipboard.
*/
write(data: ClipboardData, type?: string): Promise<void>; // {{SQL CARBON EDIT}}

/**
* Writes text to the system clipboard.
Expand Down
1 change: 1 addition & 0 deletions src/vs/platform/native/common/native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export interface ICommonNativeHostService {
writeClipboardText(text: string, type?: 'selection' | 'clipboard'): Promise<void>;
readClipboardFindText(): Promise<string>;
writeClipboardFindText(text: string): Promise<void>;
writeClipboardData(data: any, type?: 'selection' | 'clipboard'): Promise<void>; // {{SQL CARBON EDIT}}
writeClipboardBuffer(format: string, buffer: Uint8Array, type?: 'selection' | 'clipboard'): Promise<void>;
readClipboardBuffer(format: string): Promise<Uint8Array>;
hasClipboard(format: string, type?: 'selection' | 'clipboard'): Promise<boolean>;
Expand Down
5 changes: 5 additions & 0 deletions src/vs/platform/native/electron-main/nativeHostMainService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,11 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
return clipboard.writeText(text, type);
}

// {{SQL CARBON EDIT}}
async writeClipboardData(windowId: number | undefined, data: any, type?: 'selection' | 'clipboard'): Promise<void> {
return clipboard.write(data, type);
}

async readClipboardFindText(windowId: number | undefined,): Promise<string> {
return clipboard.readFindText();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { ClipboardData, IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; // {{SQL CARBON EDIT}}
import { URI } from 'vs/base/common/uri';
import { isMacintosh } from 'vs/base/common/platform';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
Expand All @@ -20,6 +20,11 @@ export class NativeClipboardService implements IClipboardService {
@INativeHostService private readonly nativeHostService: INativeHostService
) { }

// {{SQL CARBON EDIT}}
async write(data: ClipboardData, type?: 'selection' | 'clipboard'): Promise<void> {
return this.electronService.writeClipboardData(data, type);
}

async writeText(text: string, type?: 'selection' | 'clipboard'): Promise<void> {
return this.nativeHostService.writeClipboardText(text, type);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ export class TestNativeHostService implements INativeHostService {
async toggleDevTools(): Promise<void> { }
async resolveProxy(url: string): Promise<string | undefined> { return undefined; }
async readClipboardText(type?: 'selection' | 'clipboard' | undefined): Promise<string> { return ''; }
async writeClipboardData(data: any, type?: 'selection' | 'clipboard' | undefined): Promise<void> { } // {{SQL CARBON EDIT}}
async writeClipboardText(text: string, type?: 'selection' | 'clipboard' | undefined): Promise<void> { }
async readClipboardFindText(): Promise<string> { return ''; }
async writeClipboardFindText(text: string): Promise<void> { }
Expand Down

0 comments on commit 4a201b1

Please sign in to comment.