mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-31 01:25:38 -05:00
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:
@@ -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() {
|
||||
|
||||
|
||||
@@ -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<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 {
|
||||
public static ID = 'grid.maximize';
|
||||
public static LABEL = localize('maximize', "Maximize");
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<IResultMessageIntern> {
|
||||
|
||||
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<Model, IResultMessageIntern, FuzzyScore>;
|
||||
|
||||
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 = <WorkbenchDataTree<Model, IResultMessageIntern, FuzzyScore>>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 = <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<IResultMessageIntern>): 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 <IMessagesActionContext>{
|
||||
selection,
|
||||
tree
|
||||
};
|
||||
getVisibleContent(): string {
|
||||
let text = '';
|
||||
const lineDelimiter = this.textResourcePropertiesService.getEOL(URI.parse(`queryEditor:messagePanel`));
|
||||
const traverseAndAppend = (node: ITreeNode<IResultMessageIntern, FuzzyScore>) => {
|
||||
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 = <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 = <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 = <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<Model, IMessagePanelMessage | IMessagePanelBatchMessage> {
|
||||
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<void> {
|
||||
return Promise.resolve(null);
|
||||
return messages || [];
|
||||
}
|
||||
}
|
||||
|
||||
class MessageRenderer implements IRenderer {
|
||||
|
||||
getHeight(tree: ITree, element: IResultMessageIntern): number {
|
||||
class MessagePanelDelegate extends CachedListVirtualDelegate<IResultMessageIntern> {
|
||||
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<IResultMessageIntern, void, IMessageTemplate> {
|
||||
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<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;
|
||||
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<IResultMessageIntern, void>, 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 = <ICodeEditor>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 = <ICodeEditor>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<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 uuid = generateUuid();
|
||||
|
||||
public focus() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user