Rework message panel (#10177)

* wip

* fix compile

* fix look

* fix hygiene errors

* add back functionality

* fix some issues with accessibility

* proper dispose template

* handle state properly in message panel
This commit is contained in:
Anthony Dresser
2020-04-30 13:49:18 -07:00
committed by GitHub
parent 28230b67d4
commit 92e0b2e130
4 changed files with 240 additions and 276 deletions

View File

@@ -3,8 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
export interface IMessageTreeState {
readonly focus: string[];
readonly selection: string[];
readonly expanded: string[];
readonly scrollTop: number;
}
export class MessagePanelState { export class MessagePanelState {
public scrollPosition?: number; public viewState?: IMessageTreeState;
dispose() { dispose() {

View File

@@ -5,14 +5,10 @@
import { Action } from 'vs/base/common/actions'; import { Action } from 'vs/base/common/actions';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { Table } from 'sql/base/browser/ui/table/table'; import { Table } from 'sql/base/browser/ui/table/table';
import { QueryEditor } from './queryEditor'; import { QueryEditor } from './queryEditor';
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin'; import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
import { isWindows } from 'vs/base/common/platform';
import { removeAnsiEscapeCodes } from 'vs/base/common/strings';
import { IGridDataProvider } from 'sql/workbench/services/query/common/gridDataProvider'; import { IGridDataProvider } from 'sql/workbench/services/query/common/gridDataProvider';
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
import QueryRunner from 'sql/workbench/services/query/common/queryRunner'; import QueryRunner from 'sql/workbench/services/query/common/queryRunner';
@@ -35,11 +31,6 @@ export interface IGridActionContext {
resultId: number; resultId: number;
} }
export interface IMessagesActionContext {
selection: Selection;
tree: ITree;
}
function mapForNumberColumn(ranges: Slick.Range[]): Slick.Range[] { function mapForNumberColumn(ranges: Slick.Range[]): Slick.Range[] {
if (ranges) { if (ranges) {
return ranges.map(e => new Slick.Range(e.fromRow, e.fromCell - 1, e.toRow, e.toCell ? e.toCell - 1 : undefined)); return ranges.map(e => new Slick.Range(e.fromRow, e.fromCell - 1, e.toRow, e.toCell ? e.toCell - 1 : undefined));
@@ -132,49 +123,6 @@ export class SelectAllGridAction extends Action {
} }
} }
export class CopyMessagesAction extends Action {
public static ID = 'grid.messages.copy';
public static LABEL = localize('copyMessages', "Copy");
constructor(
@IClipboardService private clipboardService: IClipboardService
) {
super(CopyMessagesAction.ID, CopyMessagesAction.LABEL);
}
public run(context: IMessagesActionContext): Promise<boolean> {
this.clipboardService.writeText(context.selection.toString());
return Promise.resolve(true);
}
}
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(
private tree: ITree,
@IClipboardService private clipboardService: IClipboardService) {
super(CopyAllMessagesAction.ID, CopyAllMessagesAction.LABEL);
}
public run(): Promise<any> {
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 Promise.resolve(null);
}
}
export class MaximizeTableAction extends Action { export class MaximizeTableAction extends Action {
public static ID = 'grid.maximize'; public static ID = 'grid.maximize';
public static LABEL = localize('maximize', "Maximize"); public static LABEL = localize('maximize', "Maximize");

View File

@@ -3,22 +3,84 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
/* Disable repl hover highlight in tree. */ /* Debug repl */
.monaco-workbench .message-tree .monaco-tree .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) {
background-color: inherit; .message-tree {
height: 100%;
box-sizing: border-box;
overflow: hidden;
} }
/* Disable repl hover highlight in tree. */ .message-tree .monaco-tl-contents {
.monaco-workbench .message-tree .monaco-tree .monaco-tree-row > .content {
line-height: 18px;
user-select: text; user-select: text;
-webkit-user-select: text;
white-space: pre;
}
.message-tree.word-wrap .monaco-tl-contents {
/* Wrap words but also do not trim whitespace #6275 */
word-wrap: break-word; word-wrap: break-word;
/* white-space: pre-wrap; */ white-space: pre-wrap;
/* Break on all #7533 */
word-break: break-all; word-break: break-all;
} }
.monaco-workbench .message-tree .monaco-tree .monaco-tree-rows>.monaco-tree-row { .monaco-workbench.mac .message-tree .monaco-tl-twistie.collapsible + .monaco-tl-contents,
cursor: default; .monaco-workbench.mac .message-tree .monaco-tl-twistie {
cursor: pointer;
}
.message-tree .output.expression.value-and-source {
display: flex;
}
.message-tree .output.expression.value-and-source .value {
flex: 1;
}
.message-tree .monaco-tl-contents .arrow {
position:absolute;
left: 2px;
opacity: 0.25;
}
.vs-dark .message-tree .monaco-tl-contents .arrow {
opacity: 0.4;
}
.message-tree .output.expression.value-and-source .source {
margin-left: 4px;
margin-right: 8px;
cursor: pointer;
text-decoration: underline;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 150px;
}
.message-tree .monaco-list-row {
cursor: text;
}
.message-tree .output.expression > .value,
.message-tree .evaluation-result.expression > .value {
margin-left: 0px;
}
.message-tree .output.expression > .annotation,
.message-tree .evaluation-result.expression > .annotation {
font-size: inherit;
padding-left: 6px;
}
.message-tree .output.expression .name:not(:empty) {
margin-right: 6px;
}
/* Only show 'stale expansion' info when the element gets expanded. */
.message-tree .evaluation-result > .annotation::before {
content: '';
} }
.message-tree .time-stamp { .message-tree .time-stamp {

View File

@@ -4,31 +4,34 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/messagePanel'; import 'vs/css!./media/messagePanel';
import { IMessagesActionContext, CopyMessagesAction, CopyAllMessagesAction } from './actions';
import QueryRunner, { IQueryMessage } from 'sql/workbench/services/query/common/queryRunner'; import QueryRunner, { IQueryMessage } from 'sql/workbench/services/query/common/queryRunner';
import { IExpandableTree } from 'sql/workbench/services/objectExplorer/browser/treeUpdateUtils';
import { ISelectionData } from 'azdata'; import { ISelectionData } from 'azdata';
import { IDataSource, ITree, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree'; import { ITreeRenderer, IDataSource, ITreeNode, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree';
import { generateUuid } from 'vs/base/common/uuid'; import { generateUuid } from 'vs/base/common/uuid';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { attachListStyler } from 'vs/platform/theme/common/styler'; import { attachListStyler } from 'vs/platform/theme/common/styler';
import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { OpenMode, ClickBehavior, ICancelableEvent, IControllerOptions } from 'vs/base/parts/tree/browser/treeDefaults'; import { WorkbenchDataTree } from 'vs/platform/list/browser/listService';
import { WorkbenchTreeController } from 'vs/platform/list/browser/listService';
import { isArray, isString } from 'vs/base/common/types'; import { isArray, isString } from 'vs/base/common/types';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { $, Dimension, createStyleSheet, addStandardDisposableGenericMouseDownListner } from 'vs/base/browser/dom';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { $, Dimension, createStyleSheet } from 'vs/base/browser/dom';
import { QueryEditor } from 'sql/workbench/contrib/query/browser/queryEditor';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { resultsErrorColor } from 'sql/platform/theme/common/colors'; import { resultsErrorColor } from 'sql/platform/theme/common/colors';
import { MessagePanelState } from 'sql/workbench/common/editor/query/messagePanelState'; import { MessagePanelState } from 'sql/workbench/common/editor/query/messagePanelState';
import { CachedListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { FuzzyScore } from 'vs/base/common/filters';
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { localize } from 'vs/nls';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IAction, Action } from 'vs/base/common/actions';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
import { removeAnsiEscapeCodes } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { QueryEditor } from 'sql/workbench/contrib/query/browser/queryEditor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
export interface IResultMessageIntern { export interface IResultMessageIntern {
id?: string; id?: string;
@@ -55,6 +58,7 @@ interface IMessageTemplate {
interface IBatchTemplate extends IMessageTemplate { interface IBatchTemplate extends IMessageTemplate {
timeStamp: HTMLElement; timeStamp: HTMLElement;
disposable: DisposableStore;
} }
const TemplateIds = { const TemplateIds = {
@@ -64,93 +68,97 @@ const TemplateIds = {
ERROR: 'error' ERROR: 'error'
}; };
export class AccessibilityProvider implements IListAccessibilityProvider<IResultMessageIntern> {
getWidgetAriaLabel(): string {
return localize('messagePanel', "Message Panel");
}
getAriaLabel(element: IResultMessageIntern): string {
return element.message;
}
}
export class MessagePanel extends Disposable { export class MessagePanel extends Disposable {
private ds = new MessageDataSource();
private renderer = new MessageRenderer();
private model = new Model(); private model = new Model();
private controller: MessageController;
private container = $('.message-tree'); private container = $('.message-tree');
private styleElement = createStyleSheet(this.container); private styleElement = createStyleSheet(this.container);
private queryRunnerDisposables = this._register(new DisposableStore()); private queryRunnerDisposables = this._register(new DisposableStore());
private _state: MessagePanelState | undefined; private _state: MessagePanelState | undefined;
private tree: ITree; private tree: WorkbenchDataTree<Model, IResultMessageIntern, FuzzyScore>;
constructor( constructor(
@IInstantiationService instantiationService: IInstantiationService, @IInstantiationService instantiationService: IInstantiationService,
@IThemeService private readonly themeService: IThemeService, @IThemeService private readonly themeService: IThemeService,
@IContextMenuService private readonly contextMenuService: IContextMenuService @IContextMenuService private readonly contextMenuService: IContextMenuService,
@IClipboardService private readonly clipboardService: IClipboardService,
@ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService
) { ) {
super(); super();
this.controller = instantiationService.createInstance(MessageController, { openMode: OpenMode.SINGLE_CLICK, clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change, to preserve focus behaviour in input field */ }); this.tree = <WorkbenchDataTree<Model, IResultMessageIntern, FuzzyScore>>instantiationService.createInstance(
this.controller.toFocusOnClick = this.model; WorkbenchDataTree,
this.tree = this._register(new Tree(this.container, { 'MessagePanel',
dataSource: this.ds, this.container,
renderer: this.renderer, new MessagePanelDelegate(),
controller: this.controller [
}, { keyboardSupport: false, horizontalScrollMode: ScrollbarVisibility.Auto })); new MessageRenderer(),
new ErrorMessageRenderer(),
instantiationService.createInstance(BatchMessageRenderer)
],
new MessageDataSource(),
{
accessibilityProvider: new AccessibilityProvider(),
mouseSupport: false,
setRowLineHeight: false,
supportDynamicHeights: true
});
this._register(this.tree.onContextMenu(e => this.onContextMenu(e)));
this._register(this.tree.onDidScroll(() => this.state.viewState = this.tree.getViewState()));
this.tree.setInput(this.model); this.tree.setInput(this.model);
this.tree.onDidScroll(e => {
// convert to old VS Code tree interface with expandable methods
let expandableTree: IExpandableTree = <IExpandableTree>this.tree;
if (this.state) {
this.state.scrollPosition = expandableTree.getScrollPosition();
}
});
this.container.style.width = '100%'; this.container.style.width = '100%';
this.container.style.height = '100%'; this.container.style.height = '100%';
this._register(attachListStyler(this.tree, this.themeService)); this._register(attachListStyler(this.tree, this.themeService));
this._register(this.themeService.onDidColorThemeChange(this.applyStyles, this)); this._register(this.themeService.onDidColorThemeChange(this.applyStyles, this));
this.applyStyles(this.themeService.getColorTheme()); this.applyStyles(this.themeService.getColorTheme());
this.controller.onKeyDown = (tree, event) => { }
if (event.ctrlKey && event.code === 'KeyC') {
let context: IMessagesActionContext = {
selection: document.getSelection(),
tree: this.tree,
};
let copyMessageAction = instantiationService.createInstance(CopyMessagesAction);
copyMessageAction.run(context);
event.preventDefault();
event.stopPropagation();
return true;
}
return false;
};
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 private onContextMenu(event: ITreeContextMenuEvent<IResultMessageIntern>): void {
if (event) { // Prevent native context menu from showing up
event.preventDefault(); const actions: IAction[] = [];
event.stopPropagation(); actions.push(new Action('messagePanel.copy', localize('copy', "Copy"), undefined, true, async () => {
const nativeSelection = window.getSelection();
if (nativeSelection) {
await this.clipboardService.writeText(nativeSelection.toString());
} }
return Promise.resolve();
}));
actions.push(new Action('workbench.queryEditor.messages.action.copyAll', localize('copyAll', "Copy All"), undefined, true, async () => {
await this.clipboardService.writeText(this.getVisibleContent());
return Promise.resolve();
}));
const selection = document.getSelection(); this.contextMenuService.showContextMenu({
getAnchor: () => event.anchor,
getActions: () => actions
});
}
this.contextMenuService.showContextMenu({ getVisibleContent(): string {
getAnchor: () => { let text = '';
return { x: event.posx, y: event.posy }; const lineDelimiter = this.textResourcePropertiesService.getEOL(URI.parse(`queryEditor:messagePanel`));
}, const traverseAndAppend = (node: ITreeNode<IResultMessageIntern, FuzzyScore>) => {
getActions: () => { node.children.forEach(child => {
return [ text += child.element.message.trimRight() + lineDelimiter;
instantiationService.createInstance(CopyMessagesAction), if (!child.collapsed && child.children.length) {
instantiationService.createInstance(CopyAllMessagesAction, this.tree) traverseAndAppend(child);
];
},
getActionsContext: () => {
return <IMessagesActionContext>{
selection,
tree
};
} }
}); });
return true;
}; };
traverseAndAppend(this.tree.getNode());
return removeAnsiEscapeCodes(text);
} }
public render(container: HTMLElement): void { public render(container: HTMLElement): void {
@@ -158,22 +166,11 @@ export class MessagePanel extends Disposable {
} }
public layout(size: Dimension): void { public layout(size: Dimension): void {
// convert to old VS Code tree interface with expandable methods
let expandableTree: IExpandableTree = <IExpandableTree>this.tree;
const previousScrollPosition = expandableTree.getScrollPosition();
this.tree.layout(size.height); this.tree.layout(size.height);
if (this.state && this.state.scrollPosition) { this.tree.updateChildren();
expandableTree.setScrollPosition(this.state.scrollPosition);
} else {
if (previousScrollPosition === 1) {
expandableTree.setScrollPosition(1);
}
}
} }
public focus(): void { public focus(): void {
this.tree.refresh();
this.tree.domFocus(); this.tree.domFocus();
} }
@@ -191,23 +188,7 @@ export class MessagePanel extends Disposable {
} else { } else {
this.model.messages.push(message); this.model.messages.push(message);
} }
// convert to old VS Code tree interface with expandable methods this.tree.updateChildren();
let expandableTree: IExpandableTree = <IExpandableTree>this.tree;
if (this.state && this.state.scrollPosition) {
const previousScroll = this.state.scrollPosition;
this.tree.refresh(this.model, false).then(() => {
// Restore the previous scroll position when switching between tabs
expandableTree.setScrollPosition(previousScroll);
});
} else {
const previousScrollPosition = expandableTree.getScrollPosition();
this.tree.refresh(this.model).then(() => {
// Scroll to the end if the user was already at the end otherwise leave the current scroll position
if (previousScrollPosition === 1) {
expandableTree.setScrollPosition(1);
}
});
}
} }
private applyStyles(theme: IColorTheme): void { private applyStyles(theme: IColorTheme): void {
@@ -227,16 +208,12 @@ export class MessagePanel extends Disposable {
this.model.messages = []; this.model.messages = [];
this._state = undefined; this._state = undefined;
this.model.totalExecuteMessage = undefined; this.model.totalExecuteMessage = undefined;
this.tree.refresh(this.model); this.tree.updateChildren();
} }
public set state(val: MessagePanelState) { public set state(val: MessagePanelState) {
this._state = val; this._state = val;
// convert to old VS Code tree interface with expandable methods this.tree.setInput(this.model, val.viewState);
let expandableTree: IExpandableTree = <IExpandableTree>this.tree;
if (this.state.scrollPosition) {
expandableTree.setScrollPosition(this.state.scrollPosition);
}
} }
public get state(): MessagePanelState { public get state(): MessagePanelState {
@@ -260,48 +237,28 @@ export class MessagePanel extends Disposable {
} }
} }
class MessageDataSource implements IDataSource { class MessageDataSource implements IDataSource<Model, IMessagePanelMessage | IMessagePanelBatchMessage> {
getId(tree: ITree, element: Model | IResultMessageIntern): string { hasChildren(element: Model | IMessagePanelMessage | IMessagePanelBatchMessage): boolean {
if (element instanceof Model) {
return element.uuid;
} else {
if (!element.id) {
element.id = generateUuid();
}
return element.id;
}
}
hasChildren(tree: ITree, element: any): boolean {
return element instanceof Model; return element instanceof Model;
} }
getChildren(tree: ITree, element: any): Promise<(IMessagePanelMessage | IMessagePanelBatchMessage)[]> { getChildren(element: Model): (IMessagePanelMessage | IMessagePanelBatchMessage)[] {
if (element instanceof Model) { let messages = element.messages;
let messages = element.messages; if (element.totalExecuteMessage) {
if (element.totalExecuteMessage) { messages = messages.concat(element.totalExecuteMessage);
messages = messages.concat(element.totalExecuteMessage);
}
return Promise.resolve(messages);
} else {
return Promise.resolve(undefined);
} }
} return messages || [];
getParent(tree: ITree, element: any): Promise<void> {
return Promise.resolve(null);
} }
} }
class MessageRenderer implements IRenderer { class MessagePanelDelegate extends CachedListVirtualDelegate<IResultMessageIntern> {
protected estimateHeight(element: IResultMessageIntern): number {
getHeight(tree: ITree, element: IResultMessageIntern): number {
const lineHeight = 22; const lineHeight = 22;
let lines = element.message.split('\n').length; let lines = element.message.split('\n').length;
return lineHeight * lines; return lineHeight * lines;
} }
getTemplateId(tree: ITree, element: IResultMessageIntern): string { getTemplateId(element: IResultMessageIntern): string {
if (element instanceof Model) { if (element instanceof Model) {
return TemplateIds.MODEL; return TemplateIds.MODEL;
} else if (element.selection) { } else if (element.selection) {
@@ -313,92 +270,86 @@ class MessageRenderer implements IRenderer {
} }
} }
renderTemplate(tree: ITree, templateId: string, container: HTMLElement): IMessageTemplate | IBatchTemplate { hasDynamicHeight(element: IResultMessageIntern): boolean {
// Empty elements should not have dynamic height since they will be invisible
if (templateId === TemplateIds.MESSAGE) { return element.message.toString().length > 0;
container.append($('.time-stamp'));
const message = $('.message');
message.style.whiteSpace = 'pre';
container.append(message);
return { message };
} else if (templateId === TemplateIds.BATCH) {
const timeStamp = $('.time-stamp');
container.append(timeStamp);
const message = $('.batch-start');
message.style.whiteSpace = 'pre';
container.append(message);
return { message, timeStamp };
} else if (templateId === TemplateIds.ERROR) {
container.append($('.time-stamp'));
const message = $('.error-message');
container.append(message);
return { message };
} else {
return undefined;
}
} }
renderElement(tree: ITree, element: IResultMessageIntern, templateId: string, templateData: IMessageTemplate | IBatchTemplate): void { }
if (templateId === TemplateIds.MESSAGE || templateId === TemplateIds.ERROR) {
let data: IMessageTemplate = templateData; class ErrorMessageRenderer implements ITreeRenderer<IResultMessageIntern, void, IMessageTemplate> {
data.message.innerText = element.message; public readonly templateId = TemplateIds.ERROR;
} else if (templateId === TemplateIds.BATCH) {
let data = templateData as IBatchTemplate; renderTemplate(container: HTMLElement): IMessageTemplate {
if (isString(element.time)) { container.append($('.time-stamp'));
element.time = new Date(element.time!); const message = $('.error-message');
} container.append(message);
data.timeStamp.innerText = (element.time as Date).toLocaleTimeString(); return { message };
data.message.innerText = element.message;
}
} }
disposeTemplate(tree: ITree, templateId: string, templateData: any): void { renderElement(node: ITreeNode<IResultMessageIntern, void>, index: number, templateData: IMessageTemplate): void {
let data: IMessageTemplate = templateData;
data.message.innerText = node.element.message;
}
disposeTemplate(templateData: IMessageTemplate | IBatchTemplate): void {
} }
} }
export class MessageController extends WorkbenchTreeController { class BatchMessageRenderer implements ITreeRenderer<IResultMessageIntern, void, IBatchTemplate> {
public readonly templateId = TemplateIds.BATCH;
private lastSelectedString: string = null; constructor(@IEditorService private readonly editorService: IEditorService) { }
public toFocusOnClick: { focus(): void };
constructor( renderTemplate(container: HTMLElement): IBatchTemplate {
options: IControllerOptions, const timeStamp = $('.time-stamp');
@IConfigurationService configurationService: IConfigurationService, container.append(timeStamp);
@IEditorService private workbenchEditorService: IEditorService const message = $('.batch-start');
) { message.style.whiteSpace = 'pre';
super(options, configurationService); container.append(message);
return { message, timeStamp, disposable: new DisposableStore() };
} }
protected onLeftClick(tree: ITree, element: any, eventish: ICancelableEvent, origin: string = 'mouse'): boolean { renderElement(node: ITreeNode<IResultMessageIntern, void>, index: number, templateData: IBatchTemplate): void {
// input and output are one element in the tree => we only expand if the user clicked on the output. if (isString(node.element.time)) {
// if ((element.reference > 0 || (element instanceof RawObjectReplElement && element.hasChildren)) && mouseEvent.target.className.indexOf('input expression') === -1) { node.element.time = new Date(node.element.time!);
super.onLeftClick(tree, element, eventish, origin);
tree.clearFocus();
tree.deselect(element);
// }
const selection = window.getSelection();
if (selection.type !== 'Range' || this.lastSelectedString === selection.toString()) {
// only focus the input if the user is not currently selecting.
this.toFocusOnClick.focus();
} }
this.lastSelectedString = selection.toString(); templateData.timeStamp.innerText = (node.element.time as Date).toLocaleTimeString();
templateData.message.innerText = node.element.message;
if (element.selection) { if (node.element.selection) {
let selection: ISelectionData = element.selection; const selection = { endColumn: node.element.selection.endColumn + 1, endLineNumber: node.element.selection.endLine + 1, startColumn: node.element.selection.startColumn + 1, startLineNumber: node.element.selection.startLine + 1 };
// this is a batch statement templateData.disposable.add(addStandardDisposableGenericMouseDownListner(templateData.message, () => {
let editor = this.workbenchEditorService.activeEditorPane as QueryEditor; let editor = this.editorService.activeEditorPane as QueryEditor;
const codeEditor = <ICodeEditor>editor.getControl(); const codeEditor = <ICodeEditor>editor.getControl();
codeEditor.focus(); codeEditor.focus();
codeEditor.setSelection({ endColumn: selection.endColumn + 1, endLineNumber: selection.endLine + 1, startColumn: selection.startColumn + 1, startLineNumber: selection.startLine + 1 }); codeEditor.setSelection(selection);
codeEditor.revealRangeInCenterIfOutsideViewport({ endColumn: selection.endColumn + 1, endLineNumber: selection.endLine + 1, startColumn: selection.startColumn + 1, startLineNumber: selection.startLine + 1 }); codeEditor.revealRangeInCenterIfOutsideViewport(selection);
}));
} }
return true;
} }
public onContextMenu(tree: ITree, element: any, event: ContextMenuEvent): boolean { disposeTemplate(templateData: IBatchTemplate): void {
return true; dispose(templateData.disposable);
}
}
class MessageRenderer implements ITreeRenderer<IResultMessageIntern, void, IMessageTemplate> {
public readonly templateId = TemplateIds.MESSAGE;
renderTemplate(container: HTMLElement): IMessageTemplate {
container.append($('.time-stamp'));
const message = $('.message');
message.style.whiteSpace = 'pre';
container.append(message);
return { message };
}
renderElement(node: ITreeNode<IResultMessageIntern, void>, index: number, templateData: IMessageTemplate): void {
let data: IMessageTemplate = templateData;
data.message.innerText = node.element.message;
}
disposeTemplate(templateData: IMessageTemplate | IBatchTemplate): void {
} }
} }
@@ -407,8 +358,4 @@ export class Model {
public totalExecuteMessage: IMessagePanelMessage; public totalExecuteMessage: IMessagePanelMessage;
public uuid = generateUuid(); public uuid = generateUuid();
public focus() {
}
} }