diff --git a/src/sql/workbench/common/editor/query/messagePanelState.ts b/src/sql/workbench/common/editor/query/messagePanelState.ts index e9b6fa445a..b5a641159a 100644 --- a/src/sql/workbench/common/editor/query/messagePanelState.ts +++ b/src/sql/workbench/common/editor/query/messagePanelState.ts @@ -3,8 +3,15 @@ * 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 { - public scrollPosition?: number; + public viewState?: IMessageTreeState; dispose() { diff --git a/src/sql/workbench/contrib/query/browser/actions.ts b/src/sql/workbench/contrib/query/browser/actions.ts index d6750501f4..aed2a1db49 100644 --- a/src/sql/workbench/contrib/query/browser/actions.ts +++ b/src/sql/workbench/contrib/query/browser/actions.ts @@ -5,14 +5,10 @@ import { Action } from 'vs/base/common/actions'; 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 { Table } from 'sql/base/browser/ui/table/table'; import { QueryEditor } from './queryEditor'; 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 { INotificationService } from 'vs/platform/notification/common/notification'; import QueryRunner from 'sql/workbench/services/query/common/queryRunner'; @@ -35,11 +31,6 @@ export interface IGridActionContext { resultId: number; } -export interface IMessagesActionContext { - selection: Selection; - tree: ITree; -} - function mapForNumberColumn(ranges: Slick.Range[]): Slick.Range[] { if (ranges) { 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 { - 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 { - 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 { public static ID = 'grid.maximize'; public static LABEL = localize('maximize', "Maximize"); diff --git a/src/sql/workbench/contrib/query/browser/media/messagePanel.css b/src/sql/workbench/contrib/query/browser/media/messagePanel.css index 19d087eff2..019f23a200 100644 --- a/src/sql/workbench/contrib/query/browser/media/messagePanel.css +++ b/src/sql/workbench/contrib/query/browser/media/messagePanel.css @@ -3,22 +3,84 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* Disable repl hover highlight in tree. */ -.monaco-workbench .message-tree .monaco-tree .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) { - background-color: inherit; +/* Debug repl */ + +.message-tree { + height: 100%; + box-sizing: border-box; + overflow: hidden; } -/* Disable repl hover highlight in tree. */ -.monaco-workbench .message-tree .monaco-tree .monaco-tree-row > .content { - line-height: 18px; +.message-tree .monaco-tl-contents { 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; - /* white-space: pre-wrap; */ + white-space: pre-wrap; + /* Break on all #7533 */ word-break: break-all; } -.monaco-workbench .message-tree .monaco-tree .monaco-tree-rows>.monaco-tree-row { - cursor: default; +.monaco-workbench.mac .message-tree .monaco-tl-twistie.collapsible + .monaco-tl-contents, +.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 { diff --git a/src/sql/workbench/contrib/query/browser/messagePanel.ts b/src/sql/workbench/contrib/query/browser/messagePanel.ts index cda346b22b..b1bd5e5e11 100644 --- a/src/sql/workbench/contrib/query/browser/messagePanel.ts +++ b/src/sql/workbench/contrib/query/browser/messagePanel.ts @@ -4,31 +4,34 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/messagePanel'; -import { IMessagesActionContext, CopyMessagesAction, CopyAllMessagesAction } from './actions'; import QueryRunner, { IQueryMessage } from 'sql/workbench/services/query/common/queryRunner'; -import { IExpandableTree } from 'sql/workbench/services/objectExplorer/browser/treeUpdateUtils'; 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 { 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 { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { OpenMode, ClickBehavior, ICancelableEvent, IControllerOptions } from 'vs/base/parts/tree/browser/treeDefaults'; -import { WorkbenchTreeController } from 'vs/platform/list/browser/listService'; +import { WorkbenchDataTree } from 'vs/platform/list/browser/listService'; import { isArray, isString } from 'vs/base/common/types'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -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 { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { $, Dimension, createStyleSheet, addStandardDisposableGenericMouseDownListner } from 'vs/base/browser/dom'; import { resultsErrorColor } from 'sql/platform/theme/common/colors'; 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 { id?: string; @@ -55,6 +58,7 @@ interface IMessageTemplate { interface IBatchTemplate extends IMessageTemplate { timeStamp: HTMLElement; + disposable: DisposableStore; } const TemplateIds = { @@ -64,93 +68,97 @@ const TemplateIds = { ERROR: 'error' }; +export class AccessibilityProvider implements IListAccessibilityProvider { + + getWidgetAriaLabel(): string { + return localize('messagePanel', "Message Panel"); + } + + getAriaLabel(element: IResultMessageIntern): string { + return element.message; + } +} + export class MessagePanel extends Disposable { - private ds = new MessageDataSource(); - private renderer = new MessageRenderer(); private model = new Model(); - private controller: MessageController; private container = $('.message-tree'); private styleElement = createStyleSheet(this.container); private queryRunnerDisposables = this._register(new DisposableStore()); private _state: MessagePanelState | undefined; - private tree: ITree; + private tree: WorkbenchDataTree; constructor( @IInstantiationService instantiationService: IInstantiationService, @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(); - 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.controller.toFocusOnClick = this.model; - this.tree = this._register(new Tree(this.container, { - dataSource: this.ds, - renderer: this.renderer, - controller: this.controller - }, { keyboardSupport: false, horizontalScrollMode: ScrollbarVisibility.Auto })); + this.tree = >instantiationService.createInstance( + WorkbenchDataTree, + 'MessagePanel', + this.container, + new MessagePanelDelegate(), + [ + 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.onDidScroll(e => { - // convert to old VS Code tree interface with expandable methods - let expandableTree: IExpandableTree = this.tree; - - if (this.state) { - this.state.scrollPosition = expandableTree.getScrollPosition(); - } - }); this.container.style.width = '100%'; this.container.style.height = '100%'; this._register(attachListStyler(this.tree, this.themeService)); this._register(this.themeService.onDidColorThemeChange(this.applyStyles, this)); 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 - if (event) { - event.preventDefault(); - event.stopPropagation(); + private onContextMenu(event: ITreeContextMenuEvent): void { + // Prevent native context menu from showing up + const actions: IAction[] = []; + 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({ - getAnchor: () => { - return { x: event.posx, y: event.posy }; - }, - getActions: () => { - return [ - instantiationService.createInstance(CopyMessagesAction), - instantiationService.createInstance(CopyAllMessagesAction, this.tree) - ]; - }, - getActionsContext: () => { - return { - selection, - tree - }; + getVisibleContent(): string { + let text = ''; + const lineDelimiter = this.textResourcePropertiesService.getEOL(URI.parse(`queryEditor:messagePanel`)); + const traverseAndAppend = (node: ITreeNode) => { + node.children.forEach(child => { + text += child.element.message.trimRight() + lineDelimiter; + if (!child.collapsed && child.children.length) { + traverseAndAppend(child); } }); - - return true; }; + traverseAndAppend(this.tree.getNode()); + + return removeAnsiEscapeCodes(text); } public render(container: HTMLElement): void { @@ -158,22 +166,11 @@ export class MessagePanel extends Disposable { } public layout(size: Dimension): void { - // convert to old VS Code tree interface with expandable methods - let expandableTree: IExpandableTree = this.tree; - - const previousScrollPosition = expandableTree.getScrollPosition(); this.tree.layout(size.height); - if (this.state && this.state.scrollPosition) { - expandableTree.setScrollPosition(this.state.scrollPosition); - } else { - if (previousScrollPosition === 1) { - expandableTree.setScrollPosition(1); - } - } + this.tree.updateChildren(); } public focus(): void { - this.tree.refresh(); this.tree.domFocus(); } @@ -191,23 +188,7 @@ export class MessagePanel extends Disposable { } else { this.model.messages.push(message); } - // convert to old VS Code tree interface with expandable methods - let expandableTree: 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); - } - }); - } + this.tree.updateChildren(); } private applyStyles(theme: IColorTheme): void { @@ -227,16 +208,12 @@ export class MessagePanel extends Disposable { this.model.messages = []; this._state = undefined; this.model.totalExecuteMessage = undefined; - this.tree.refresh(this.model); + this.tree.updateChildren(); } public set state(val: MessagePanelState) { this._state = val; - // convert to old VS Code tree interface with expandable methods - let expandableTree: IExpandableTree = this.tree; - if (this.state.scrollPosition) { - expandableTree.setScrollPosition(this.state.scrollPosition); - } + this.tree.setInput(this.model, val.viewState); } public get state(): MessagePanelState { @@ -260,48 +237,28 @@ export class MessagePanel extends Disposable { } } -class MessageDataSource implements IDataSource { - getId(tree: ITree, element: Model | IResultMessageIntern): string { - if (element instanceof Model) { - return element.uuid; - } else { - if (!element.id) { - element.id = generateUuid(); - } - return element.id; - } - } - - hasChildren(tree: ITree, element: any): boolean { +class MessageDataSource implements IDataSource { + hasChildren(element: Model | IMessagePanelMessage | IMessagePanelBatchMessage): boolean { return element instanceof Model; } - getChildren(tree: ITree, element: any): Promise<(IMessagePanelMessage | IMessagePanelBatchMessage)[]> { - if (element instanceof Model) { - let messages = element.messages; - if (element.totalExecuteMessage) { - messages = messages.concat(element.totalExecuteMessage); - } - return Promise.resolve(messages); - } else { - return Promise.resolve(undefined); + getChildren(element: Model): (IMessagePanelMessage | IMessagePanelBatchMessage)[] { + let messages = element.messages; + if (element.totalExecuteMessage) { + messages = messages.concat(element.totalExecuteMessage); } - } - - getParent(tree: ITree, element: any): Promise { - return Promise.resolve(null); + return messages || []; } } -class MessageRenderer implements IRenderer { - - getHeight(tree: ITree, element: IResultMessageIntern): number { +class MessagePanelDelegate extends CachedListVirtualDelegate { + protected estimateHeight(element: IResultMessageIntern): number { const lineHeight = 22; let lines = element.message.split('\n').length; return lineHeight * lines; } - getTemplateId(tree: ITree, element: IResultMessageIntern): string { + getTemplateId(element: IResultMessageIntern): string { if (element instanceof Model) { return TemplateIds.MODEL; } else if (element.selection) { @@ -313,92 +270,86 @@ class MessageRenderer implements IRenderer { } } - renderTemplate(tree: ITree, templateId: string, container: HTMLElement): IMessageTemplate | IBatchTemplate { - - if (templateId === TemplateIds.MESSAGE) { - 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; - } + hasDynamicHeight(element: IResultMessageIntern): boolean { + // Empty elements should not have dynamic height since they will be invisible + return element.message.toString().length > 0; } - renderElement(tree: ITree, element: IResultMessageIntern, templateId: string, templateData: IMessageTemplate | IBatchTemplate): void { - if (templateId === TemplateIds.MESSAGE || templateId === TemplateIds.ERROR) { - let data: IMessageTemplate = templateData; - data.message.innerText = element.message; - } else if (templateId === TemplateIds.BATCH) { - let data = templateData as IBatchTemplate; - if (isString(element.time)) { - element.time = new Date(element.time!); - } - data.timeStamp.innerText = (element.time as Date).toLocaleTimeString(); - data.message.innerText = element.message; - } +} + +class ErrorMessageRenderer implements ITreeRenderer { + public readonly templateId = TemplateIds.ERROR; + + renderTemplate(container: HTMLElement): IMessageTemplate { + container.append($('.time-stamp')); + const message = $('.error-message'); + container.append(message); + return { message }; } - disposeTemplate(tree: ITree, templateId: string, templateData: any): void { + renderElement(node: ITreeNode, 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 { + public readonly templateId = TemplateIds.BATCH; - private lastSelectedString: string = null; - public toFocusOnClick: { focus(): void }; + constructor(@IEditorService private readonly editorService: IEditorService) { } - constructor( - options: IControllerOptions, - @IConfigurationService configurationService: IConfigurationService, - @IEditorService private workbenchEditorService: IEditorService - ) { - super(options, configurationService); + renderTemplate(container: HTMLElement): IBatchTemplate { + const timeStamp = $('.time-stamp'); + container.append(timeStamp); + const message = $('.batch-start'); + message.style.whiteSpace = 'pre'; + container.append(message); + return { message, timeStamp, disposable: new DisposableStore() }; } - protected onLeftClick(tree: ITree, element: any, eventish: ICancelableEvent, origin: string = 'mouse'): boolean { - // input and output are one element in the tree => we only expand if the user clicked on the output. - // if ((element.reference > 0 || (element instanceof RawObjectReplElement && element.hasChildren)) && mouseEvent.target.className.indexOf('input expression') === -1) { - 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(); + renderElement(node: ITreeNode, index: number, templateData: IBatchTemplate): void { + if (isString(node.element.time)) { + node.element.time = new Date(node.element.time!); } - this.lastSelectedString = selection.toString(); - - if (element.selection) { - let selection: ISelectionData = element.selection; - // this is a batch statement - let editor = this.workbenchEditorService.activeEditorPane as QueryEditor; - const codeEditor = editor.getControl(); - codeEditor.focus(); - codeEditor.setSelection({ endColumn: selection.endColumn + 1, endLineNumber: selection.endLine + 1, startColumn: selection.startColumn + 1, startLineNumber: selection.startLine + 1 }); - codeEditor.revealRangeInCenterIfOutsideViewport({ endColumn: selection.endColumn + 1, endLineNumber: selection.endLine + 1, startColumn: selection.startColumn + 1, startLineNumber: selection.startLine + 1 }); + templateData.timeStamp.innerText = (node.element.time as Date).toLocaleTimeString(); + templateData.message.innerText = node.element.message; + if (node.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 }; + templateData.disposable.add(addStandardDisposableGenericMouseDownListner(templateData.message, () => { + let editor = this.editorService.activeEditorPane as QueryEditor; + const codeEditor = editor.getControl(); + codeEditor.focus(); + codeEditor.setSelection(selection); + codeEditor.revealRangeInCenterIfOutsideViewport(selection); + })); } - - return true; } - public onContextMenu(tree: ITree, element: any, event: ContextMenuEvent): boolean { - return true; + disposeTemplate(templateData: IBatchTemplate): void { + dispose(templateData.disposable); + } +} + +class MessageRenderer implements ITreeRenderer { + 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, 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 uuid = generateUuid(); - - public focus() { - - } }