diff --git a/src/sql/parts/query/editor/actions.ts b/src/sql/parts/query/editor/actions.ts index ce68aa0846..78cb7b8717 100644 --- a/src/sql/parts/query/editor/actions.ts +++ b/src/sql/parts/query/editor/actions.ts @@ -19,6 +19,9 @@ import { Table } from 'sql/base/browser/ui/table/table'; import { GridTableState } from 'sql/parts/query/editor/gridPanel'; import { QueryEditor } from './queryEditor'; import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin'; +import { MessagePanel } from 'sql/parts/query/editor/messagePanel'; +import { isWindows } from 'vs/base/common/platform'; +import { removeAnsiEscapeCodes } from 'vs/base/common/strings'; export interface IGridActionContext { cell: { row: number; cell: number; }; @@ -129,6 +132,7 @@ export class CopyMessagesAction extends Action { public static LABEL = localize('copyMessages', 'Copy'); constructor( + private messagePanel: MessagePanel, @IClipboardService private clipboardService: IClipboardService ) { super(CopyMessagesAction.ID, CopyMessagesAction.LABEL); @@ -140,21 +144,31 @@ export class CopyMessagesAction extends Action { } } -export class SelectAllMessagesAction extends Action { - public static ID = 'grid.messages.selectAll'; - public static LABEL = localize('selectAll', 'Select All'); +const lineDelimiter = isWindows ? '\r\n' : '\n'; +export class CopyAllMessagesAction extends Action { + public static ID = 'grid.messages.copyAll'; + public static LABEL = localize('copyAll', "Copy All"); - constructor() { - super(SelectAllMessagesAction.ID, SelectAllMessagesAction.LABEL); + constructor( + private tree: ITree, + @IClipboardService private clipboardService: IClipboardService) + { + super(CopyAllMessagesAction.ID, CopyAllMessagesAction.LABEL); } - public run(context: IMessagesActionContext): TPromise { - let range = document.createRange(); - range.selectNodeContents(context.tree.getHTMLElement()); - let sel = document.getSelection(); - sel.removeAllRanges(); - sel.addRange(range); - return TPromise.as(true); + public run(): TPromise { + let text = ''; + const navigator = this.tree.getNavigator(); + // skip first navigator element - the root node + while (navigator.next()) { + if (text) { + text += lineDelimiter; + } + text += (navigator.current()).message; + } + + this.clipboardService.writeText(removeAnsiEscapeCodes(text)); + return TPromise.as(null); } } diff --git a/src/sql/parts/query/editor/messagePanel.ts b/src/sql/parts/query/editor/messagePanel.ts index 5ea53b1fc9..28b1fc7564 100644 --- a/src/sql/parts/query/editor/messagePanel.ts +++ b/src/sql/parts/query/editor/messagePanel.ts @@ -5,7 +5,7 @@ 'use strict'; import 'vs/css!./media/messagePanel'; -import { IMessagesActionContext, SelectAllMessagesAction, CopyMessagesAction } from './actions'; +import { IMessagesActionContext, CopyMessagesAction, CopyAllMessagesAction } from './actions'; import QueryRunner from 'sql/parts/query/execution/queryRunner'; import { QueryInput } from 'sql/parts/query/common/queryInput'; @@ -30,6 +30,7 @@ import { isArray, isUndefinedOrNull } from 'vs/base/common/types'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; export interface IResultMessageIntern extends IResultMessage { id?: string; @@ -88,6 +89,7 @@ export class MessagePanel extends ViewletPanel { private _state: MessagePanelState; private tree: ITree; + private _selectAllMessages: boolean; constructor( options: IViewletPanelOptions, @@ -95,7 +97,8 @@ export class MessagePanel extends ViewletPanel { @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, @IThemeService private themeService: IThemeService, - @IInstantiationService instantiationService: IInstantiationService + @IInstantiationService instantiationService: IInstantiationService, + @IClipboardService private clipboardService: IClipboardService ) { super(options, keybindingService, contextMenuService, configurationService); this.controller = instantiationService.createInstance(MessageController, { openMode: OpenMode.SINGLE_CLICK, clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change, to preserve focus behaviour in input field */ }); @@ -116,6 +119,55 @@ export class MessagePanel extends ViewletPanel { this.state.collapsed = !this.isExpanded(); } }); + this.controller.onKeyDown = (tree, event) => { + if (event.ctrlKey) { + let context: IMessagesActionContext = { + selection: document.getSelection(), + tree: this.tree, + }; + // Ctrl + C for copy + if (event.code === 'KeyC') { + let copyMessageAction = instantiationService.createInstance(CopyMessagesAction, this, this.clipboardService); + copyMessageAction.run(context); + } + } + event.preventDefault(); + event.stopPropagation(); + return true; + }; + this.controller.onContextMenu = (tree, element, event) => { + if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') { + return false; // allow context menu on input fields + } + + // Prevent native context menu from showing up + if (event) { + event.preventDefault(); + event.stopPropagation(); + } + + const selection = document.getSelection(); + + this.contextMenuService.showContextMenu({ + getAnchor: () => { + return { x: event.posx, y: event.posy }; + }, + getActions: () => { + return TPromise.as([ + instantiationService.createInstance(CopyMessagesAction, this, this.clipboardService), + instantiationService.createInstance(CopyAllMessagesAction, this.tree, this.clipboardService) + ]); + }, + getActionsContext: () => { + return { + selection, + tree + }; + } + }); + + return true; + }; } protected renderBody(container: HTMLElement): void { @@ -346,36 +398,6 @@ export class MessageController extends WorkbenchTreeController { } public onContextMenu(tree: ITree, element: any, event: ContextMenuEvent): boolean { - if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') { - return false; // allow context menu on input fields - } - - // Prevent native context menu from showing up - if (event) { - event.preventDefault(); - event.stopPropagation(); - } - - const selection = document.getSelection(); - - this.contextMenuService.showContextMenu({ - getAnchor: () => { - return { x: event.posx, y: event.posy }; - }, - getActions: () => { - return TPromise.as([ - this.instantiationService.createInstance(CopyMessagesAction), - new SelectAllMessagesAction() - ]); - }, - getActionsContext: () => { - return { - selection, - tree - }; - } - }); - return true; } }