mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
137
src/sql/parts/profiler/contrib/profiler.contribution.ts
Normal file
137
src/sql/parts/profiler/contrib/profiler.contribution.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor';
|
||||
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
|
||||
import { ProfilerEditor } from 'sql/parts/profiler/editor/profilerEditor';
|
||||
import { PROFILER_SESSION_TEMPLATE_SETTINGS, IProfilerSessionTemplate } from 'sql/parts/profiler/service/interfaces';
|
||||
|
||||
const profilerDescriptor = new EditorDescriptor(
|
||||
ProfilerEditor.ID,
|
||||
'Profiler',
|
||||
'sql/parts/profiler/editor/profilerEditor',
|
||||
'ProfilerEditor'
|
||||
);
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
|
||||
.registerEditor(profilerDescriptor, [new SyncDescriptor(ProfilerInput)]);
|
||||
|
||||
const profilerSessionTemplateSchema: IJSONSchema = {
|
||||
description: nls.localize('profiler.settings.sessionTemplates', "Specifies session templates"),
|
||||
type: 'array',
|
||||
items: <IJSONSchema>{
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
},
|
||||
default: <Array<IProfilerSessionTemplate>>[
|
||||
{
|
||||
name: 'Standard',
|
||||
events: [
|
||||
{
|
||||
name: 'Audit Login',
|
||||
optionalColumns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime', 'BinaryData']
|
||||
},
|
||||
{
|
||||
name: 'Audit Logout',
|
||||
optionalColumns: ['ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime']
|
||||
},
|
||||
{
|
||||
name: 'ExistingConnection',
|
||||
optionalColumns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData']
|
||||
},
|
||||
{
|
||||
name: 'RPC:Completed',
|
||||
optionalColumns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData']
|
||||
},
|
||||
{
|
||||
name: 'SQL:BatchCompleted',
|
||||
optionalColumns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData']
|
||||
},
|
||||
{
|
||||
name: 'SQL:BatchStarting',
|
||||
optionalColumns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime']
|
||||
}
|
||||
],
|
||||
view: {
|
||||
events: [
|
||||
{
|
||||
name: 'Audit Login',
|
||||
columns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime']
|
||||
},
|
||||
{
|
||||
name: 'Audit Logout',
|
||||
columns: ['ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime']
|
||||
},
|
||||
{
|
||||
name: 'ExistingConnection',
|
||||
columns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime']
|
||||
},
|
||||
{
|
||||
name: 'RPC:Completed',
|
||||
columns: ['ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData']
|
||||
},
|
||||
{
|
||||
name: 'SQL:BatchCompleted',
|
||||
columns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData']
|
||||
},
|
||||
{
|
||||
name: 'SQL:BatchStarting',
|
||||
columns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime']
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'TSQL'
|
||||
},
|
||||
{
|
||||
name: 'Blank'
|
||||
},
|
||||
{
|
||||
name: 'SP_Counts'
|
||||
},
|
||||
{
|
||||
name: 'TQL_Duration'
|
||||
},
|
||||
{
|
||||
name: 'TSQL_Grouped'
|
||||
},
|
||||
{
|
||||
name: 'TSQL_Locks'
|
||||
},
|
||||
{
|
||||
name: 'TSQL_Replay'
|
||||
},
|
||||
{
|
||||
name: 'TSQL_SPs'
|
||||
},
|
||||
{
|
||||
name: 'Tuning'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
|
||||
const dashboardConfig: IConfigurationNode = {
|
||||
id: 'Profiler',
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PROFILER_SESSION_TEMPLATE_SETTINGS]: profilerSessionTemplateSchema
|
||||
}
|
||||
};
|
||||
|
||||
configurationRegistry.registerConfiguration(dashboardConfig);
|
||||
@@ -0,0 +1,32 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { GlobalNewProfilerAction } from './profilerWorkbenchActions';
|
||||
|
||||
import { registerTask } from 'sql/platform/tasks/taskRegistry';
|
||||
import { NewProfilerAction } from './profilerActions';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
|
||||
// Contribute Global Actions
|
||||
const category = nls.localize('profilerCategory', "Profiler");
|
||||
|
||||
const newProfilerSchema: IJSONSchema = {
|
||||
description: nls.localize('carbon.actions.newProfiler', 'Open up a new profiler window'),
|
||||
type: 'null',
|
||||
default: null
|
||||
};
|
||||
|
||||
if (process.env['VSCODE_DEV']) {
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalNewProfilerAction, GlobalNewProfilerAction.ID, GlobalNewProfilerAction.LABEL), 'Profiler: New Profiler', category);
|
||||
|
||||
registerTask('new-profiler', '', newProfilerSchema, NewProfilerAction);
|
||||
}
|
||||
244
src/sql/parts/profiler/contrib/profilerActions.ts
Normal file
244
src/sql/parts/profiler/contrib/profilerActions.ts
Normal file
@@ -0,0 +1,244 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { IProfilerService } from 'sql/parts/profiler/service/interfaces';
|
||||
import { IProfilerController } from 'sql/parts/profiler/editor/controller/interfaces';
|
||||
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
|
||||
import { ITaskActionContext, TaskAction } from 'sql/workbench/common/actions';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IEditorAction } from 'vs/editor/common/editorCommon';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export class ProfilerConnect extends Action {
|
||||
public static ID = 'profiler.connect';
|
||||
public static LABEL = nls.localize('connect', "Connect");
|
||||
|
||||
private _connected: boolean = false;
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IProfilerService private _profilerService: IProfilerService
|
||||
) {
|
||||
super(id, label, 'connect');
|
||||
}
|
||||
|
||||
public run(input: ProfilerInput): TPromise<boolean> {
|
||||
this.enabled = false;
|
||||
if (!this._connected) {
|
||||
return TPromise.wrap(this._profilerService.connectSession(input.id).then(() => {
|
||||
this.enabled = true;
|
||||
this.connected = true;
|
||||
input.state.change({ isConnected: true, isRunning: false, isPaused: false, isStopped: true });
|
||||
return true;
|
||||
}));
|
||||
} else {
|
||||
return TPromise.wrap(this._profilerService.disconnectSession(input.id).then(() => {
|
||||
this.enabled = true;
|
||||
this.connected = false;
|
||||
input.state.change({ isConnected: false, isRunning: false, isPaused: false, isStopped: false });
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
public set connected(value: boolean) {
|
||||
this._connected = value;
|
||||
this._setClass(value ? 'disconnect' : 'connect');
|
||||
this._setLabel(value ? nls.localize('disconnect', 'Disconnected') : nls.localize('connect', "Connect"));
|
||||
}
|
||||
|
||||
public get connected(): boolean {
|
||||
return this._connected;
|
||||
}
|
||||
}
|
||||
|
||||
export class ProfilerStart extends Action {
|
||||
public static ID = 'profiler.start';
|
||||
public static LABEL = nls.localize('start', "Start");
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IProfilerService private _profilerService: IProfilerService
|
||||
) {
|
||||
super(id, label, 'start');
|
||||
}
|
||||
|
||||
public run(input: ProfilerInput): TPromise<boolean> {
|
||||
this.enabled = false;
|
||||
return TPromise.wrap(this._profilerService.startSession(input.id).then(() => {
|
||||
input.state.change({ isRunning: true, isStopped: false, isPaused: false });
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class ProfilerPause extends Action {
|
||||
public static ID = 'profiler.pause';
|
||||
public static LABEL = nls.localize('pause', "Pause");
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IProfilerService private _profilerService: IProfilerService
|
||||
) {
|
||||
super(id, label, 'stop');
|
||||
}
|
||||
|
||||
public run(input: ProfilerInput): TPromise<boolean> {
|
||||
this.enabled = false;
|
||||
return TPromise.wrap(this._profilerService.pauseSession(input.id).then(() => {
|
||||
input.state.change({ isPaused: true, isStopped: false, isRunning: false });
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export class ProfilerStop extends Action {
|
||||
public static ID = 'profiler.stop';
|
||||
public static LABEL = nls.localize('stop', "Stop");
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IProfilerService private _profilerService: IProfilerService
|
||||
) {
|
||||
super(id, label, 'stop');
|
||||
}
|
||||
|
||||
public run(input: ProfilerInput): TPromise<boolean> {
|
||||
this.enabled = false;
|
||||
return TPromise.wrap(this._profilerService.stopSession(input.id).then(() => {
|
||||
input.state.change({ isStopped: true, isPaused: false, isRunning: false });
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export class ProfilerClear extends Action {
|
||||
public static ID = 'profiler.clear';
|
||||
public static LABEL = nls.localize('profiler.clear', "Clear Data");
|
||||
|
||||
constructor(id: string, label: string) {
|
||||
super(id, label, 'stop');
|
||||
}
|
||||
|
||||
run(input: ProfilerInput): TPromise<void> {
|
||||
input.data.clear();
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
export class ProfilerAutoScroll extends Action {
|
||||
public static ID = 'profiler.autoscroll';
|
||||
public static LABEL = nls.localize('profiler.toggleAutoscroll', "Toggle Auto Scroll");
|
||||
|
||||
constructor(id: string, label: string) {
|
||||
super(id, label, 'stop');
|
||||
}
|
||||
|
||||
run(input: ProfilerInput): TPromise<boolean> {
|
||||
this.checked = !this.checked;
|
||||
input.state.change({ autoscroll: this.checked });
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class ProfilerCollapsablePanelAction extends Action {
|
||||
public static ID = 'profiler.toggleCollapsePanel';
|
||||
public static LABEL = nls.localize('profiler.toggleCollapsePanel', "Toggle Collapsed Panel");
|
||||
|
||||
private _collapsed: boolean;
|
||||
|
||||
constructor(id: string, label: string) {
|
||||
super(id, label, 'minimize-panel-action');
|
||||
}
|
||||
|
||||
public run(input: ProfilerInput): TPromise<boolean> {
|
||||
this.collapsed = !this._collapsed;
|
||||
input.state.change({ isPanelCollapsed: this._collapsed });
|
||||
return TPromise.as(true);
|
||||
}
|
||||
|
||||
set collapsed(val: boolean) {
|
||||
this._collapsed = val === false ? false : true;
|
||||
this._setClass(this._collapsed ? 'maximize-panel-action' : 'minimize-panel-action');
|
||||
}
|
||||
}
|
||||
|
||||
export class ProfilerEditColumns extends Action {
|
||||
public static ID = 'profiler.';
|
||||
public static LABEL = nls.localize('profiler.editColumns', "Edit Columns");
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IProfilerService private _profilerService: IProfilerService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(input: ProfilerInput): TPromise<boolean> {
|
||||
return TPromise.wrap(this._profilerService.launchColumnEditor(input)).then(() => true);
|
||||
}
|
||||
}
|
||||
|
||||
export class ProfilerFindNext implements IEditorAction {
|
||||
public readonly id = 'profiler.findNext';
|
||||
public readonly label = nls.localize('profiler.findNext', "Find Next String");
|
||||
public readonly alias = '';
|
||||
|
||||
constructor(private profiler: IProfilerController) { }
|
||||
|
||||
run(): TPromise<void> {
|
||||
this.profiler.findNext();
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
isSupported(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class ProfilerFindPrevious implements IEditorAction {
|
||||
public readonly id = 'profiler.findPrevious';
|
||||
public readonly label = nls.localize('profiler.findPrevious', "Find Previous String");
|
||||
public readonly alias = '';
|
||||
|
||||
constructor(private profiler: IProfilerController) { }
|
||||
|
||||
run(): TPromise<void> {
|
||||
this.profiler.findPrevious();
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
isSupported(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class NewProfilerAction extends TaskAction {
|
||||
public static ID = 'newProfiler';
|
||||
public static LABEL = nls.localize('newProfiler', 'New Profiler');
|
||||
public static ICON = 'profile';
|
||||
|
||||
constructor(
|
||||
id: string, label: string, icon: string,
|
||||
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService
|
||||
) {
|
||||
super(id, label, icon);
|
||||
}
|
||||
|
||||
run(actionContext: ITaskActionContext): TPromise<boolean> {
|
||||
let profilerInput = this._instantiationService.createInstance(ProfilerInput, actionContext.profile);
|
||||
return this._editorService.openEditor(profilerInput, { pinned: true }, false).then(() => {
|
||||
return TPromise.as(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
32
src/sql/parts/profiler/contrib/profilerWorkbenchActions.ts
Normal file
32
src/sql/parts/profiler/contrib/profilerWorkbenchActions.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export class GlobalNewProfilerAction extends Action {
|
||||
public static ID = 'explorer.newProfiler';
|
||||
public static LABEL = nls.localize('newProfiler', "New Profiler");
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(context?: any): TPromise<boolean> {
|
||||
let profilerInput = this._instantiationService.createInstance(ProfilerInput, context ? context.connectionProfile : undefined);
|
||||
return this._editorService.openEditor(profilerInput, { pinned: true }, false).then(() => TPromise.as(true));
|
||||
}
|
||||
}
|
||||
13
src/sql/parts/profiler/dialog/media/profilerDialog.css
Normal file
13
src/sql/parts/profiler/dialog/media/profilerDialog.css
Normal file
@@ -0,0 +1,13 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.profiler-column-tree {
|
||||
height: calc(100% - 25px);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tree-row > * {
|
||||
display: inline-block;
|
||||
}
|
||||
409
src/sql/parts/profiler/dialog/profilerColumnEditorDialog.ts
Normal file
409
src/sql/parts/profiler/dialog/profilerColumnEditorDialog.ts
Normal file
@@ -0,0 +1,409 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
import 'vs/css!./media/profilerDialog';
|
||||
|
||||
import { Modal } from 'sql/base/browser/ui/modal/modal';
|
||||
import { attachModalDialogStyler } from 'sql/common/theme/styler';
|
||||
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
|
||||
import * as TelemetryKeys from 'sql/common/telemetryKeys';
|
||||
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { Builder } from 'vs/base/browser/builder';
|
||||
import { SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { IDataSource, ITree, IRenderer } from 'vs/base/parts/tree/browser/tree';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
class EventItem {
|
||||
|
||||
constructor(
|
||||
private _name: string,
|
||||
private _parent: SessionItem,
|
||||
private _columns?: Array<ColumnItem>,
|
||||
) {
|
||||
if (!_columns) {
|
||||
this._columns = new Array<ColumnItem>();
|
||||
}
|
||||
}
|
||||
|
||||
public hasChildren(): boolean {
|
||||
return this._columns && this._columns.length > 0;
|
||||
}
|
||||
|
||||
public getChildren(): Array<ColumnItem> {
|
||||
return this._columns;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
public addColumn(...columns: Array<ColumnItem>) {
|
||||
this._columns = this._columns.concat(columns);
|
||||
}
|
||||
|
||||
public get parent(): SessionItem {
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
public get selected(): boolean {
|
||||
return this._columns.every(i => i.selected);
|
||||
}
|
||||
|
||||
public set selected(val: boolean) {
|
||||
this._columns.forEach(i => i.selected = val);
|
||||
}
|
||||
|
||||
public get indeterminate(): boolean {
|
||||
return this._columns.some(i => i.selected) && !this.selected;
|
||||
}
|
||||
}
|
||||
|
||||
class ColumnItem {
|
||||
public selected: boolean;
|
||||
public readonly indeterminate = false;
|
||||
constructor(
|
||||
private _name: string,
|
||||
private _parent: EventItem
|
||||
) { }
|
||||
|
||||
public get id(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
public get parent(): EventItem {
|
||||
return this._parent;
|
||||
}
|
||||
}
|
||||
|
||||
class ColumnSortedColumnItem {
|
||||
constructor(
|
||||
private _name: string,
|
||||
private _parent: SessionItem
|
||||
) { }
|
||||
|
||||
public get id(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
public get parent(): SessionItem {
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
public get selected(): boolean {
|
||||
return this._parent.getUnsortedChildren()
|
||||
.every(e => e.getChildren().filter(c => c.id === this.id)
|
||||
.every(c => c.selected));
|
||||
}
|
||||
|
||||
public set selected(val: boolean) {
|
||||
this._parent.getUnsortedChildren()
|
||||
.forEach(e => e.getChildren()
|
||||
.filter(c => c.id === this.id)
|
||||
.forEach(c => c.selected = val));
|
||||
}
|
||||
|
||||
public get indeterminate(): boolean {
|
||||
return this._parent.getUnsortedChildren()
|
||||
.some(e => e.getChildren()
|
||||
.filter(c => c.id === this.id)
|
||||
.some(c => c.selected))
|
||||
&& !this.selected;
|
||||
}
|
||||
}
|
||||
|
||||
class SessionItem {
|
||||
private _sortedColumnItems: Array<ColumnSortedColumnItem> = [];
|
||||
constructor(
|
||||
private _name: string,
|
||||
private _sort: 'event' | 'column',
|
||||
private _events?: Array<EventItem>
|
||||
) {
|
||||
if (!_events) {
|
||||
this._events = new Array<EventItem>();
|
||||
} else {
|
||||
_events.forEach(e => {
|
||||
e.getChildren().forEach(c => {
|
||||
if (!this._sortedColumnItems.some(i => i.id === c.id)) {
|
||||
this._sortedColumnItems.push(new ColumnSortedColumnItem(c.id, this));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
public hasChildren(): boolean {
|
||||
if (this._sort === 'event') {
|
||||
return this._events && this._events.length > 0;
|
||||
} else {
|
||||
return this._events && this._events.some(i => i.hasChildren());
|
||||
}
|
||||
}
|
||||
|
||||
public getUnsortedChildren(): Array<EventItem> {
|
||||
return this._events;
|
||||
}
|
||||
|
||||
public getChildren(): Array<EventItem | ColumnSortedColumnItem> {
|
||||
if (this._sort === 'event') {
|
||||
return this._events;
|
||||
} else {
|
||||
return this._sortedColumnItems;
|
||||
}
|
||||
}
|
||||
|
||||
public addEvents(...events: Array<EventItem>) {
|
||||
this._events = this._events.concat(events);
|
||||
events.forEach(e => {
|
||||
e.getChildren().forEach(c => {
|
||||
if (!this._sortedColumnItems.some(i => i.id === c.id)) {
|
||||
this._sortedColumnItems.push(new ColumnSortedColumnItem(c.id, this));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public changeSort(type: 'event' | 'column'): void {
|
||||
this._sort = type;
|
||||
}
|
||||
}
|
||||
|
||||
class TreeRenderer implements IRenderer {
|
||||
|
||||
private _onSelectedChange = new Emitter<any>();
|
||||
public onSelectedChange: Event<any> = this._onSelectedChange.event;
|
||||
|
||||
getHeight(tree: ITree, element: any): number {
|
||||
return 22;
|
||||
}
|
||||
|
||||
getTemplateId(tree: ITree, element: any): string {
|
||||
if (element instanceof SessionItem) {
|
||||
return 'session';
|
||||
} else if (element instanceof EventItem) {
|
||||
return 'event';
|
||||
} else if (element instanceof ColumnItem) {
|
||||
return 'column';
|
||||
} else if (element instanceof ColumnSortedColumnItem) {
|
||||
return 'columnSorted';
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
renderTemplate(tree: ITree, templateId: string, container: HTMLElement): RenderTemplate {
|
||||
let data = Object.create(null);
|
||||
let row = document.createElement('div');
|
||||
row.className = 'tree-row';
|
||||
DOM.append(container, row);
|
||||
data.toDispose = [];
|
||||
data.checkbox = document.createElement('input');
|
||||
DOM.append(row, data.checkbox);
|
||||
data.checkbox.type = 'checkbox';
|
||||
data.toDispose.push(DOM.addStandardDisposableListener(data.checkbox, 'change', () => {
|
||||
data.context.selected = !data.context.selected;
|
||||
this._onSelectedChange.fire(data.context);
|
||||
}));
|
||||
data.label = document.createElement('div');
|
||||
DOM.append(row, data.label);
|
||||
return data;
|
||||
}
|
||||
|
||||
renderElement(tree: ITree, element: any, templateId: string, templateData: RenderTemplate): void {
|
||||
templateData.context = element;
|
||||
templateData.label.innerText = element.id;
|
||||
templateData.checkbox.checked = element.selected;
|
||||
templateData.checkbox.indeterminate = element.indeterminate;
|
||||
}
|
||||
|
||||
disposeTemplate(tree: ITree, templateId: string, templateData: RenderTemplate): void {
|
||||
dispose(templateData.toDispose);
|
||||
}
|
||||
}
|
||||
|
||||
interface RenderTemplate {
|
||||
label: HTMLElement;
|
||||
toDispose: Array<IDisposable>;
|
||||
checkbox: HTMLInputElement;
|
||||
context?: any;
|
||||
}
|
||||
|
||||
class TreeDataSource implements IDataSource {
|
||||
|
||||
getId(tree: ITree, element: any): string {
|
||||
if (element instanceof EventItem) {
|
||||
return element.parent.id + element.id;
|
||||
} else if (element instanceof ColumnItem) {
|
||||
return element.parent.parent.id + element.parent.id + element.id;
|
||||
} else if (element instanceof SessionItem) {
|
||||
return element.id;
|
||||
} else if (element instanceof ColumnSortedColumnItem) {
|
||||
return element.id;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
hasChildren(tree: ITree, element: any): boolean {
|
||||
if (element instanceof SessionItem) {
|
||||
return element.hasChildren();
|
||||
} else if (element instanceof EventItem) {
|
||||
return element.hasChildren();
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
getChildren(tree: ITree, element: any): TPromise<Array<any>> {
|
||||
if (element instanceof EventItem) {
|
||||
return TPromise.as(element.getChildren());
|
||||
} else if (element instanceof SessionItem) {
|
||||
return TPromise.as(element.getChildren());
|
||||
} else {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
getParent(tree: ITree, element: any): TPromise<any> {
|
||||
if (element instanceof ColumnItem) {
|
||||
return TPromise.as(element.parent);
|
||||
} else if (element instanceof EventItem) {
|
||||
return TPromise.as(element.parent);
|
||||
} else if (element instanceof ColumnSortedColumnItem) {
|
||||
return TPromise.as(element.parent);
|
||||
} else {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
shouldAutoexpand?(tree: ITree, element: any): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class ProfilerColumnEditorDialog extends Modal {
|
||||
|
||||
private _selectBox: SelectBox;
|
||||
private _selectedValue: number = 0;
|
||||
private readonly _options = [
|
||||
nls.localize('eventSort', "Sort by event"),
|
||||
nls.localize('nameColumn', "Sort by column")
|
||||
];
|
||||
private _tree: Tree;
|
||||
private _input: ProfilerInput;
|
||||
private _element: SessionItem;
|
||||
private _treeContainer: HTMLElement;
|
||||
|
||||
constructor(
|
||||
@IPartService _partService: IPartService,
|
||||
@IThemeService private _themeService: IThemeService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService
|
||||
) {
|
||||
super(nls.localize('profiler', 'Profiler'), TelemetryKeys.Profiler, _partService, telemetryService, contextKeyService);
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
super.render();
|
||||
this._register(attachModalDialogStyler(this, this._themeService));
|
||||
this.addFooterButton(nls.localize('ok', "OK"), () => this.onAccept(undefined));
|
||||
this.addFooterButton(nls.localize('cancel', "Cancel"), () => this.onClose(undefined));
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement): void {
|
||||
let builder = new Builder(container);
|
||||
builder.div({}, b => {
|
||||
this._selectBox = new SelectBox(this._options, 0);
|
||||
this._selectBox.render(b.getHTMLElement());
|
||||
this._register(this._selectBox.onDidSelect(e => {
|
||||
this._selectedValue = e.index;
|
||||
this._element.changeSort(e.index === 0 ? 'event' : 'column');
|
||||
this._tree.refresh(this._element, true);
|
||||
}));
|
||||
});
|
||||
|
||||
builder.div({ 'class': 'profiler-column-tree' }, b => {
|
||||
this._treeContainer = b.getHTMLElement();
|
||||
let renderer = new TreeRenderer();
|
||||
this._tree = new Tree(this._treeContainer, { dataSource: new TreeDataSource(), renderer });
|
||||
this._register(renderer.onSelectedChange(e => this._tree.refresh(e, true)));
|
||||
this._register(attachListStyler(this._tree, this._themeService));
|
||||
});
|
||||
}
|
||||
|
||||
public open(input: ProfilerInput): void {
|
||||
super.show();
|
||||
this._input = input;
|
||||
this._updateList();
|
||||
}
|
||||
|
||||
protected onAccept(e: StandardKeyboardEvent): void {
|
||||
this._updateInput();
|
||||
super.onAccept(e);
|
||||
}
|
||||
|
||||
private _updateInput(): void {
|
||||
this._element.getUnsortedChildren().forEach(e => {
|
||||
let origEvent = this._input.sessionTemplate.view.events.find(i => i.name === e.id);
|
||||
if (e.indeterminate) {
|
||||
e.getChildren().forEach(c => {
|
||||
if (origEvent.columns.includes(c.id) && !c.selected) {
|
||||
origEvent.columns = origEvent.columns.filter(i => i !== c.id);
|
||||
} else if (!origEvent.columns.includes(c.id) && c.selected) {
|
||||
origEvent.columns.push(c.id);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
origEvent.columns = e.getChildren()
|
||||
.filter(c => c.selected)
|
||||
.map(c => c.id);
|
||||
}
|
||||
});
|
||||
let newColumns = this._input.sessionTemplate.view.events.reduce<Array<string>>((p, e) => {
|
||||
e.columns.forEach(c => {
|
||||
if (!p.includes(c)) {
|
||||
p.push(c);
|
||||
}
|
||||
});
|
||||
return p;
|
||||
}, []);
|
||||
newColumns.unshift('EventClass');
|
||||
this._input.setColumns(newColumns);
|
||||
}
|
||||
|
||||
private _updateList(): void {
|
||||
this._element = new SessionItem(this._input.sessionTemplate.name, this._selectedValue === 0 ? 'event' : 'column');
|
||||
this._input.sessionTemplate.events.forEach(item => {
|
||||
let event = new EventItem(item.name, this._element);
|
||||
item.optionalColumns.forEach(col => {
|
||||
let column = new ColumnItem(col, event);
|
||||
column.selected = this._input.sessionTemplate.view.events.find(i => i.name === event.id).columns.includes(col);
|
||||
event.addColumn(column);
|
||||
});
|
||||
this._element.addEvents(event);
|
||||
});
|
||||
this._tree.setInput(this._element);
|
||||
this._tree.layout(DOM.getTotalHeight(this._treeContainer));
|
||||
}
|
||||
|
||||
protected layout(height?: number): void {
|
||||
this._tree.layout(DOM.getContentHeight(this._treeContainer));
|
||||
}
|
||||
|
||||
}
|
||||
9
src/sql/parts/profiler/editor/controller/interfaces.ts
Normal file
9
src/sql/parts/profiler/editor/controller/interfaces.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export interface IProfilerController {
|
||||
findNext(): void;
|
||||
findPrevious(): void;
|
||||
}
|
||||
647
src/sql/parts/profiler/editor/controller/profilerFindWidget.ts
Normal file
647
src/sql/parts/profiler/editor/controller/profilerFindWidget.ts
Normal file
@@ -0,0 +1,647 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!vs/editor/contrib/find/browser/findWidget';
|
||||
import * as nls from 'vs/nls';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput';
|
||||
import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import { Sash, IHorizontalSashLayoutProvider, ISashEvent, Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
|
||||
import { FIND_IDS, MATCHES_LIMIT } from 'vs/editor/contrib/find/common/findModel';
|
||||
import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/common/findState';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { CONTEXT_FIND_INPUT_FOCUSED } from 'vs/editor/contrib/find/common/findController';
|
||||
import { ITheme, registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { editorFindRangeHighlight, editorFindMatch, editorFindMatchHighlight, activeContrastBorder, contrastBorder, inputBackground, editorWidgetBackground, inputActiveOptionBorder, widgetShadow, inputForeground, inputBorder, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationErrorBackground, inputValidationErrorBorder, errorForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IEditorAction } from 'vs/editor/common/editorCommon';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find");
|
||||
const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find");
|
||||
const NLS_PREVIOUS_MATCH_BTN_LABEL = nls.localize('label.previousMatchButton', "Previous match");
|
||||
const NLS_NEXT_MATCH_BTN_LABEL = nls.localize('label.nextMatchButton', "Next match");
|
||||
const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close");
|
||||
const NLS_MATCHES_COUNT_LIMIT_TITLE = nls.localize('title.matchesCountLimit', "Only the first 999 results are highlighted, but all find operations work on the entire text.");
|
||||
const NLS_MATCHES_LOCATION = nls.localize('label.matchesLocation', "{0} of {1}");
|
||||
const NLS_NO_RESULTS = nls.localize('label.noResults', "No Results");
|
||||
|
||||
const FIND_WIDGET_INITIAL_WIDTH = 411;
|
||||
const PART_WIDTH = 275;
|
||||
const FIND_INPUT_AREA_WIDTH = PART_WIDTH - 54;
|
||||
|
||||
let MAX_MATCHES_COUNT_WIDTH = 69;
|
||||
|
||||
export const ACTION_IDS = {
|
||||
FIND_NEXT: 'findNext',
|
||||
FIND_PREVIOUS: 'findPrev'
|
||||
};
|
||||
|
||||
export interface ITableController {
|
||||
focus(): void;
|
||||
getConfiguration(): any;
|
||||
layoutOverlayWidget(widget: IOverlayWidget): void;
|
||||
addOverlayWidget(widget: IOverlayWidget): void;
|
||||
getAction(id: string): IEditorAction;
|
||||
onDidChangeConfiguration(fn: (e: IConfigurationChangedEvent) => void): IDisposable;
|
||||
}
|
||||
|
||||
export interface IConfigurationChangedEvent {
|
||||
layoutInfo?: boolean;
|
||||
}
|
||||
|
||||
export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSashLayoutProvider {
|
||||
private static ID = 'editor.contrib.findWidget';
|
||||
private _tableController: ITableController;
|
||||
private _state: FindReplaceState;
|
||||
private _contextViewProvider: IContextViewProvider;
|
||||
private _keybindingService: IKeybindingService;
|
||||
|
||||
private _domNode: HTMLElement;
|
||||
private _findInput: FindInput;
|
||||
|
||||
private _matchesCount: HTMLElement;
|
||||
private _prevBtn: SimpleButton;
|
||||
private _nextBtn: SimpleButton;
|
||||
private _closeBtn: SimpleButton;
|
||||
|
||||
private _isVisible: boolean;
|
||||
|
||||
private _focusTracker: dom.IFocusTracker;
|
||||
private _findInputFocussed: IContextKey<boolean>;
|
||||
|
||||
private _resizeSash: Sash;
|
||||
|
||||
constructor(
|
||||
tableController: ITableController,
|
||||
state: FindReplaceState,
|
||||
contextViewProvider: IContextViewProvider,
|
||||
keybindingService: IKeybindingService,
|
||||
contextKeyService: IContextKeyService,
|
||||
themeService: IThemeService
|
||||
) {
|
||||
super();
|
||||
this._tableController = tableController;
|
||||
this._state = state;
|
||||
this._contextViewProvider = contextViewProvider;
|
||||
this._keybindingService = keybindingService;
|
||||
|
||||
this._isVisible = false;
|
||||
|
||||
this._register(this._state.addChangeListener((e) => this._onStateChanged(e)));
|
||||
this._buildDomNode();
|
||||
this._updateButtons();
|
||||
|
||||
let checkEditorWidth = () => {
|
||||
let editorWidth = this._tableController.getConfiguration().layoutInfo.width;
|
||||
let collapsedFindWidget = false;
|
||||
let reducedFindWidget = false;
|
||||
let narrowFindWidget = false;
|
||||
let widgetWidth = dom.getTotalWidth(this._domNode);
|
||||
|
||||
if (widgetWidth > FIND_WIDGET_INITIAL_WIDTH) {
|
||||
// as the widget is resized by users, we may need to change the max width of the widget as the editor width changes.
|
||||
this._domNode.style.maxWidth = `${editorWidth - 28 - 15}px`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (FIND_WIDGET_INITIAL_WIDTH + 28 >= editorWidth) {
|
||||
reducedFindWidget = true;
|
||||
}
|
||||
if (FIND_WIDGET_INITIAL_WIDTH + 28 - MAX_MATCHES_COUNT_WIDTH >= editorWidth) {
|
||||
narrowFindWidget = true;
|
||||
}
|
||||
if (FIND_WIDGET_INITIAL_WIDTH + 28 - MAX_MATCHES_COUNT_WIDTH >= editorWidth + 50) {
|
||||
collapsedFindWidget = true;
|
||||
}
|
||||
dom.toggleClass(this._domNode, 'collapsed-find-widget', collapsedFindWidget);
|
||||
dom.toggleClass(this._domNode, 'narrow-find-widget', narrowFindWidget);
|
||||
dom.toggleClass(this._domNode, 'reduced-find-widget', reducedFindWidget);
|
||||
|
||||
if (!narrowFindWidget && !collapsedFindWidget) {
|
||||
// the minimal left offset of findwidget is 15px.
|
||||
this._domNode.style.maxWidth = `${editorWidth - 28 - 15}px`;
|
||||
}
|
||||
|
||||
};
|
||||
checkEditorWidth();
|
||||
|
||||
this._register(this._tableController.onDidChangeConfiguration((e: IConfigurationChangedEvent) => {
|
||||
if (e.layoutInfo) {
|
||||
checkEditorWidth();
|
||||
}
|
||||
}));
|
||||
|
||||
this._findInputFocussed = CONTEXT_FIND_INPUT_FOCUSED.bindTo(contextKeyService);
|
||||
this._focusTracker = this._register(dom.trackFocus(this._findInput.inputBox.inputElement));
|
||||
this._focusTracker.addFocusListener(() => {
|
||||
this._findInputFocussed.set(true);
|
||||
});
|
||||
this._focusTracker.addBlurListener(() => {
|
||||
this._findInputFocussed.set(false);
|
||||
});
|
||||
|
||||
this._tableController.addOverlayWidget(this);
|
||||
|
||||
this._applyTheme(themeService.getTheme());
|
||||
this._register(themeService.onThemeChange(this._applyTheme.bind(this)));
|
||||
}
|
||||
|
||||
// ----- IOverlayWidget API
|
||||
|
||||
public getId(): string {
|
||||
return FindWidget.ID;
|
||||
}
|
||||
|
||||
public getDomNode(): HTMLElement {
|
||||
return this._domNode;
|
||||
}
|
||||
|
||||
public getPosition(): IOverlayWidgetPosition {
|
||||
if (this._isVisible) {
|
||||
return {
|
||||
preference: OverlayWidgetPositionPreference.TOP_RIGHT_CORNER
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ----- React to state changes
|
||||
|
||||
private _onStateChanged(e: FindReplaceStateChangedEvent): void {
|
||||
if (e.searchString) {
|
||||
this._findInput.setValue(this._state.searchString);
|
||||
this._updateButtons();
|
||||
}
|
||||
if (e.isRevealed) {
|
||||
if (this._state.isRevealed) {
|
||||
this._reveal(true);
|
||||
} else {
|
||||
this._hide(true);
|
||||
}
|
||||
}
|
||||
if (e.isRegex) {
|
||||
this._findInput.setRegex(this._state.isRegex);
|
||||
}
|
||||
if (e.wholeWord) {
|
||||
this._findInput.setWholeWords(this._state.wholeWord);
|
||||
}
|
||||
if (e.matchCase) {
|
||||
this._findInput.setCaseSensitive(this._state.matchCase);
|
||||
}
|
||||
if (e.searchString || e.matchesCount || e.matchesPosition) {
|
||||
let showRedOutline = (this._state.searchString.length > 0 && this._state.matchesCount === 0);
|
||||
dom.toggleClass(this._domNode, 'no-results', showRedOutline);
|
||||
|
||||
this._updateMatchesCount();
|
||||
}
|
||||
}
|
||||
|
||||
private _updateMatchesCount(): void {
|
||||
this._matchesCount.style.minWidth = MAX_MATCHES_COUNT_WIDTH + 'px';
|
||||
if (this._state.matchesCount >= MATCHES_LIMIT) {
|
||||
this._matchesCount.title = NLS_MATCHES_COUNT_LIMIT_TITLE;
|
||||
} else {
|
||||
this._matchesCount.title = '';
|
||||
}
|
||||
|
||||
// remove previous content
|
||||
if (this._matchesCount.firstChild) {
|
||||
this._matchesCount.removeChild(this._matchesCount.firstChild);
|
||||
}
|
||||
|
||||
let label: string;
|
||||
if (this._state.matchesCount > 0) {
|
||||
let matchesCount: string = String(this._state.matchesCount);
|
||||
if (this._state.matchesCount >= MATCHES_LIMIT) {
|
||||
matchesCount += '+';
|
||||
}
|
||||
let matchesPosition: string = String(this._state.matchesPosition);
|
||||
if (matchesPosition === '0') {
|
||||
matchesPosition = '?';
|
||||
}
|
||||
label = strings.format(NLS_MATCHES_LOCATION, matchesPosition, matchesCount);
|
||||
} else {
|
||||
label = NLS_NO_RESULTS;
|
||||
}
|
||||
this._matchesCount.appendChild(document.createTextNode(label));
|
||||
|
||||
MAX_MATCHES_COUNT_WIDTH = Math.max(MAX_MATCHES_COUNT_WIDTH, this._matchesCount.clientWidth);
|
||||
}
|
||||
|
||||
// ----- actions
|
||||
|
||||
private _updateButtons(): void {
|
||||
this._findInput.setEnabled(this._isVisible);
|
||||
this._closeBtn.setEnabled(this._isVisible);
|
||||
|
||||
let findInputIsNonEmpty = (this._state.searchString.length > 0);
|
||||
this._prevBtn.setEnabled(this._isVisible && findInputIsNonEmpty);
|
||||
this._nextBtn.setEnabled(this._isVisible && findInputIsNonEmpty);
|
||||
}
|
||||
|
||||
private _reveal(animate: boolean): void {
|
||||
if (!this._isVisible) {
|
||||
this._isVisible = true;
|
||||
|
||||
this._updateButtons();
|
||||
|
||||
setTimeout(() => {
|
||||
dom.addClass(this._domNode, 'visible');
|
||||
this._domNode.setAttribute('aria-hidden', 'false');
|
||||
if (!animate) {
|
||||
dom.addClass(this._domNode, 'noanimation');
|
||||
setTimeout(() => {
|
||||
dom.removeClass(this._domNode, 'noanimation');
|
||||
}, 200);
|
||||
}
|
||||
}, 0);
|
||||
this._tableController.layoutOverlayWidget(this);
|
||||
}
|
||||
}
|
||||
|
||||
private _hide(focusTheEditor: boolean): void {
|
||||
if (this._isVisible) {
|
||||
this._isVisible = false;
|
||||
|
||||
this._updateButtons();
|
||||
|
||||
dom.removeClass(this._domNode, 'visible');
|
||||
this._domNode.setAttribute('aria-hidden', 'true');
|
||||
if (focusTheEditor) {
|
||||
this._tableController.focus();
|
||||
}
|
||||
this._tableController.layoutOverlayWidget(this);
|
||||
}
|
||||
}
|
||||
|
||||
private _applyTheme(theme: ITheme) {
|
||||
let inputStyles: IFindInputStyles = {
|
||||
inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder),
|
||||
inputBackground: theme.getColor(inputBackground),
|
||||
inputForeground: theme.getColor(inputForeground),
|
||||
inputBorder: theme.getColor(inputBorder),
|
||||
inputValidationInfoBackground: theme.getColor(inputValidationInfoBackground),
|
||||
inputValidationInfoBorder: theme.getColor(inputValidationInfoBorder),
|
||||
inputValidationWarningBackground: theme.getColor(inputValidationWarningBackground),
|
||||
inputValidationWarningBorder: theme.getColor(inputValidationWarningBorder),
|
||||
inputValidationErrorBackground: theme.getColor(inputValidationErrorBackground),
|
||||
inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder)
|
||||
};
|
||||
this._findInput.style(inputStyles);
|
||||
}
|
||||
|
||||
// ----- Public
|
||||
|
||||
public focusFindInput(): void {
|
||||
this._findInput.select();
|
||||
// Edge browser requires focus() in addition to select()
|
||||
this._findInput.focus();
|
||||
}
|
||||
|
||||
public highlightFindOptions(): void {
|
||||
this._findInput.highlightFindOptions();
|
||||
}
|
||||
|
||||
private _onFindInputMouseDown(e: IMouseEvent): void {
|
||||
// on linux, middle key does pasting.
|
||||
if (e.middleButton) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
private _onFindInputKeyDown(e: IKeyboardEvent): void {
|
||||
|
||||
if (e.equals(KeyCode.Enter)) {
|
||||
this._tableController.getAction(ACTION_IDS.FIND_NEXT).run().done(null, onUnexpectedError);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.equals(KeyMod.Shift | KeyCode.Enter)) {
|
||||
this._tableController.getAction(ACTION_IDS.FIND_NEXT).run().done(null, onUnexpectedError);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.equals(KeyCode.Tab)) {
|
||||
this._findInput.focusOnCaseSensitive();
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.equals(KeyMod.CtrlCmd | KeyCode.DownArrow)) {
|
||||
this._tableController.focus();
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ----- sash
|
||||
public getHorizontalSashTop(sash: Sash): number {
|
||||
return 0;
|
||||
}
|
||||
public getHorizontalSashLeft?(sash: Sash): number {
|
||||
return 0;
|
||||
}
|
||||
public getHorizontalSashWidth?(sash: Sash): number {
|
||||
return 500;
|
||||
}
|
||||
|
||||
// ----- initialization
|
||||
|
||||
private _keybindingLabelFor(actionId: string): string {
|
||||
let kb = this._keybindingService.lookupKeybinding(actionId);
|
||||
if (!kb) {
|
||||
return '';
|
||||
}
|
||||
return ` (${kb.getLabel()})`;
|
||||
}
|
||||
|
||||
private _buildFindPart(): HTMLElement {
|
||||
// Find input
|
||||
this._findInput = this._register(new FindInput(null, this._contextViewProvider, {
|
||||
width: FIND_INPUT_AREA_WIDTH,
|
||||
label: NLS_FIND_INPUT_LABEL,
|
||||
placeholder: NLS_FIND_INPUT_PLACEHOLDER,
|
||||
appendCaseSensitiveLabel: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand),
|
||||
appendWholeWordsLabel: this._keybindingLabelFor(FIND_IDS.ToggleWholeWordCommand),
|
||||
appendRegexLabel: this._keybindingLabelFor(FIND_IDS.ToggleRegexCommand),
|
||||
validation: (value: string): InputBoxMessage => {
|
||||
if (value.length === 0) {
|
||||
return null;
|
||||
}
|
||||
if (!this._findInput.getRegex()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
/* tslint:disable:no-unused-expression */
|
||||
new RegExp(value);
|
||||
/* tslint:enable:no-unused-expression */
|
||||
return null;
|
||||
} catch (e) {
|
||||
return { content: e.message };
|
||||
}
|
||||
}
|
||||
}));
|
||||
this._findInput.setRegex(!!this._state.isRegex);
|
||||
this._findInput.setCaseSensitive(!!this._state.matchCase);
|
||||
this._findInput.setWholeWords(!!this._state.wholeWord);
|
||||
this._register(this._findInput.onKeyDown((e) => this._onFindInputKeyDown(e)));
|
||||
this._register(this._findInput.onInput(() => {
|
||||
this._state.change({ searchString: this._findInput.getValue() }, true);
|
||||
}));
|
||||
this._register(this._findInput.onDidOptionChange(() => {
|
||||
this._state.change({
|
||||
isRegex: this._findInput.getRegex(),
|
||||
wholeWord: this._findInput.getWholeWords(),
|
||||
matchCase: this._findInput.getCaseSensitive()
|
||||
}, true);
|
||||
}));
|
||||
if (platform.isLinux) {
|
||||
this._register(this._findInput.onMouseDown((e) => this._onFindInputMouseDown(e)));
|
||||
}
|
||||
|
||||
this._matchesCount = document.createElement('div');
|
||||
this._matchesCount.className = 'matchesCount';
|
||||
this._updateMatchesCount();
|
||||
|
||||
// Previous button
|
||||
this._prevBtn = this._register(new SimpleButton({
|
||||
label: NLS_PREVIOUS_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.PreviousMatchFindAction),
|
||||
className: 'previous',
|
||||
onTrigger: () => {
|
||||
this._tableController.getAction(ACTION_IDS.FIND_PREVIOUS).run().done(null, onUnexpectedError);
|
||||
},
|
||||
onKeyDown: (e) => { }
|
||||
}));
|
||||
|
||||
// Next button
|
||||
this._nextBtn = this._register(new SimpleButton({
|
||||
label: NLS_NEXT_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.NextMatchFindAction),
|
||||
className: 'next',
|
||||
onTrigger: () => {
|
||||
this._tableController.getAction(ACTION_IDS.FIND_NEXT).run().done(null, onUnexpectedError);
|
||||
},
|
||||
onKeyDown: (e) => { }
|
||||
}));
|
||||
|
||||
let findPart = document.createElement('div');
|
||||
findPart.className = 'find-part';
|
||||
findPart.appendChild(this._findInput.domNode);
|
||||
findPart.appendChild(this._matchesCount);
|
||||
findPart.appendChild(this._prevBtn.domNode);
|
||||
findPart.appendChild(this._nextBtn.domNode);
|
||||
|
||||
// Close button
|
||||
this._closeBtn = this._register(new SimpleButton({
|
||||
label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.CloseFindWidgetCommand),
|
||||
className: 'close-fw',
|
||||
onTrigger: () => {
|
||||
this._state.change({ isRevealed: false, searchScope: null }, false);
|
||||
},
|
||||
onKeyDown: () => { }
|
||||
}));
|
||||
|
||||
findPart.appendChild(this._closeBtn.domNode);
|
||||
|
||||
return findPart;
|
||||
}
|
||||
|
||||
private _buildDomNode(): void {
|
||||
// Find part
|
||||
let findPart = this._buildFindPart();
|
||||
|
||||
// Widget
|
||||
this._domNode = document.createElement('div');
|
||||
this._domNode.className = 'editor-widget find-widget';
|
||||
this._domNode.setAttribute('aria-hidden', 'true');
|
||||
|
||||
this._domNode.appendChild(findPart);
|
||||
|
||||
this._buildSash();
|
||||
}
|
||||
|
||||
private _buildSash() {
|
||||
this._resizeSash = new Sash(this._domNode, this, { orientation: Orientation.VERTICAL });
|
||||
let originalWidth = FIND_WIDGET_INITIAL_WIDTH;
|
||||
|
||||
this._register(this._resizeSash.addListener('start', (e: ISashEvent) => {
|
||||
originalWidth = dom.getTotalWidth(this._domNode);
|
||||
}));
|
||||
|
||||
this._register(this._resizeSash.addListener('change', (evt: ISashEvent) => {
|
||||
let width = originalWidth + evt.startX - evt.currentX;
|
||||
|
||||
if (width < FIND_WIDGET_INITIAL_WIDTH) {
|
||||
// narrow down the find widget should be handled by CSS.
|
||||
return;
|
||||
}
|
||||
|
||||
let maxWidth = parseFloat(dom.getComputedStyle(this._domNode).maxWidth) || 0;
|
||||
if (width > maxWidth) {
|
||||
return;
|
||||
}
|
||||
this._domNode.style.width = `${width}px`;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
interface ISimpleCheckboxOpts {
|
||||
parent: HTMLElement;
|
||||
title: string;
|
||||
onChange: () => void;
|
||||
}
|
||||
|
||||
class SimpleCheckbox extends Widget {
|
||||
|
||||
private static _COUNTER = 0;
|
||||
|
||||
private _opts: ISimpleCheckboxOpts;
|
||||
private _domNode: HTMLElement;
|
||||
private _checkbox: HTMLInputElement;
|
||||
private _label: HTMLLabelElement;
|
||||
|
||||
constructor(opts: ISimpleCheckboxOpts) {
|
||||
super();
|
||||
this._opts = opts;
|
||||
|
||||
this._domNode = document.createElement('div');
|
||||
this._domNode.className = 'monaco-checkbox';
|
||||
this._domNode.title = this._opts.title;
|
||||
this._domNode.tabIndex = 0;
|
||||
|
||||
this._checkbox = document.createElement('input');
|
||||
this._checkbox.type = 'checkbox';
|
||||
this._checkbox.className = 'checkbox';
|
||||
this._checkbox.id = 'checkbox-' + SimpleCheckbox._COUNTER++;
|
||||
this._checkbox.tabIndex = -1;
|
||||
|
||||
this._label = document.createElement('label');
|
||||
this._label.className = 'label';
|
||||
// Connect the label and the checkbox. Checkbox will get checked when the label recieves a click.
|
||||
this._label.htmlFor = this._checkbox.id;
|
||||
this._label.tabIndex = -1;
|
||||
|
||||
this._domNode.appendChild(this._checkbox);
|
||||
this._domNode.appendChild(this._label);
|
||||
|
||||
this._opts.parent.appendChild(this._domNode);
|
||||
|
||||
this.onchange(this._checkbox, (e) => {
|
||||
this._opts.onChange();
|
||||
});
|
||||
}
|
||||
|
||||
public get domNode(): HTMLElement {
|
||||
return this._domNode;
|
||||
}
|
||||
|
||||
public get checked(): boolean {
|
||||
return this._checkbox.checked;
|
||||
}
|
||||
|
||||
public set checked(newValue: boolean) {
|
||||
this._checkbox.checked = newValue;
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this._checkbox.focus();
|
||||
}
|
||||
|
||||
private enable(): void {
|
||||
this._checkbox.removeAttribute('disabled');
|
||||
}
|
||||
|
||||
private disable(): void {
|
||||
this._checkbox.disabled = true;
|
||||
}
|
||||
|
||||
public setEnabled(enabled: boolean): void {
|
||||
if (enabled) {
|
||||
this.enable();
|
||||
this.domNode.tabIndex = 0;
|
||||
} else {
|
||||
this.disable();
|
||||
this.domNode.tabIndex = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ISimpleButtonOpts {
|
||||
label: string;
|
||||
className: string;
|
||||
onTrigger: () => void;
|
||||
onKeyDown: (e: IKeyboardEvent) => void;
|
||||
}
|
||||
|
||||
class SimpleButton extends Widget {
|
||||
|
||||
private _opts: ISimpleButtonOpts;
|
||||
private _domNode: HTMLElement;
|
||||
|
||||
constructor(opts: ISimpleButtonOpts) {
|
||||
super();
|
||||
this._opts = opts;
|
||||
|
||||
this._domNode = document.createElement('div');
|
||||
this._domNode.title = this._opts.label;
|
||||
this._domNode.tabIndex = 0;
|
||||
this._domNode.className = 'button ' + this._opts.className;
|
||||
this._domNode.setAttribute('role', 'button');
|
||||
this._domNode.setAttribute('aria-label', this._opts.label);
|
||||
|
||||
this.onclick(this._domNode, (e) => {
|
||||
this._opts.onTrigger();
|
||||
e.preventDefault();
|
||||
});
|
||||
this.onkeydown(this._domNode, (e) => {
|
||||
if (e.equals(KeyCode.Space) || e.equals(KeyCode.Enter)) {
|
||||
this._opts.onTrigger();
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
this._opts.onKeyDown(e);
|
||||
});
|
||||
}
|
||||
|
||||
public get domNode(): HTMLElement {
|
||||
return this._domNode;
|
||||
}
|
||||
|
||||
public isEnabled(): boolean {
|
||||
return (this._domNode.tabIndex >= 0);
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this._domNode.focus();
|
||||
}
|
||||
|
||||
public setEnabled(enabled: boolean): void {
|
||||
dom.toggleClass(this._domNode, 'disabled', !enabled);
|
||||
this._domNode.setAttribute('aria-disabled', String(!enabled));
|
||||
this._domNode.tabIndex = enabled ? 0 : -1;
|
||||
}
|
||||
|
||||
public setExpanded(expanded: boolean): void {
|
||||
this._domNode.setAttribute('aria-expanded', String(!!expanded));
|
||||
}
|
||||
|
||||
public toggleClass(className: string, shouldHaveIt: boolean): void {
|
||||
dom.toggleClass(this._domNode, className, shouldHaveIt);
|
||||
}
|
||||
}
|
||||
217
src/sql/parts/profiler/editor/controller/profilerTableEditor.ts
Normal file
217
src/sql/parts/profiler/editor/controller/profilerTableEditor.ts
Normal file
@@ -0,0 +1,217 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IProfilerController } from './interfaces';
|
||||
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { attachTableStyler } from 'sql/common/theme/styler';
|
||||
import { RowSelectionModel } from 'sql/base/browser/ui/table/plugins/rowSelectionModel.plugin';
|
||||
import { IProfilerStateChangedEvent } from 'sql/parts/profiler/editor/profilerState';
|
||||
import { FindWidget, ITableController, IConfigurationChangedEvent, ACTION_IDS } from './profilerFindWidget';
|
||||
import { ProfilerFindNext, ProfilerFindPrevious } from 'sql/parts/profiler/contrib/profilerActions';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IEditorAction } from 'vs/editor/common/editorCommon';
|
||||
import { IOverlayWidget } from 'vs/editor/browser/editorBrowser';
|
||||
import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/common/findState';
|
||||
import { Dimension, Builder } from 'vs/base/browser/builder';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export class ProfilerTableEditor extends BaseEditor implements IProfilerController, ITableController {
|
||||
|
||||
public static ID: string = 'workbench.editor.profiler.table';
|
||||
protected _input: ProfilerInput;
|
||||
private _profilerTable: Table<Slick.SlickData>;
|
||||
private _columnListener: IDisposable;
|
||||
private _stateListener: IDisposable;
|
||||
private _findCountChangeListener: IDisposable;
|
||||
private _findState: FindReplaceState;
|
||||
private _finder: FindWidget;
|
||||
private _overlay: HTMLElement;
|
||||
private _currentDimensions: Dimension;
|
||||
private _actionMap: { [x: string]: IEditorAction } = {};
|
||||
|
||||
private _onDidChangeConfiguration = new Emitter<IConfigurationChangedEvent>();
|
||||
public onDidChangeConfiguration: Event<IConfigurationChangedEvent> = this._onDidChangeConfiguration.event;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IWorkbenchThemeService private _themeService: IWorkbenchThemeService,
|
||||
@IContextViewService private _contextViewService: IContextViewService,
|
||||
@IKeybindingService private _keybindingService: IKeybindingService,
|
||||
@IContextKeyService private _contextKeyService: IContextKeyService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService
|
||||
) {
|
||||
super(ProfilerTableEditor.ID, telemetryService, _themeService);
|
||||
this._actionMap[ACTION_IDS.FIND_NEXT] = this._instantiationService.createInstance(ProfilerFindNext, this);
|
||||
this._actionMap[ACTION_IDS.FIND_PREVIOUS] = this._instantiationService.createInstance(ProfilerFindPrevious, this);
|
||||
}
|
||||
|
||||
public createEditor(parent: Builder): void {
|
||||
|
||||
this._overlay = document.createElement('div');
|
||||
this._overlay.className = 'overlayWidgets';
|
||||
this._overlay.style.width = '100%';
|
||||
this._overlay.style.zIndex = '4';
|
||||
parent.getHTMLElement().appendChild(this._overlay);
|
||||
|
||||
this._profilerTable = new Table(parent.getHTMLElement());
|
||||
this._profilerTable.setSelectionModel(new RowSelectionModel());
|
||||
attachTableStyler(this._profilerTable, this._themeService);
|
||||
|
||||
this._findState = new FindReplaceState();
|
||||
this._findState.addChangeListener(e => this._onFindStateChange(e));
|
||||
|
||||
this._finder = new FindWidget(
|
||||
this,
|
||||
this._findState,
|
||||
this._contextViewService,
|
||||
this._keybindingService,
|
||||
this._contextKeyService,
|
||||
this._themeService
|
||||
);
|
||||
}
|
||||
|
||||
public setInput(input: ProfilerInput): TPromise<void> {
|
||||
this._input = input;
|
||||
if (this._columnListener) {
|
||||
this._columnListener.dispose();
|
||||
}
|
||||
this._columnListener = input.onColumnsChanged(e => {
|
||||
this._profilerTable.columns = e;
|
||||
this._profilerTable.autosizeColumns();
|
||||
});
|
||||
if (this._stateListener) {
|
||||
this._stateListener.dispose();
|
||||
}
|
||||
this._stateListener = input.state.addChangeListener(e => this._onStateChange(e));
|
||||
|
||||
if (this._findCountChangeListener) {
|
||||
this._findCountChangeListener.dispose();
|
||||
}
|
||||
this._findCountChangeListener = input.data.onFindCountChange(() => this._updateFinderMatchState());
|
||||
|
||||
this._profilerTable.setData(input.data);
|
||||
this._profilerTable.columns = input.columns;
|
||||
this._profilerTable.autosizeColumns();
|
||||
this._input.data.currentFindPosition.then(val => {
|
||||
this._profilerTable.setActiveCell(val.row, val.col);
|
||||
this._updateFinderMatchState();
|
||||
}, er => { });
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public toggleSearch(): void {
|
||||
this._findState.change({
|
||||
isRevealed: true
|
||||
}, false);
|
||||
this._finder.focusFindInput();
|
||||
}
|
||||
|
||||
public findNext(): void {
|
||||
this._input.data.findNext().then(p => {
|
||||
this._profilerTable.setActiveCell(p.row, p.col);
|
||||
this._updateFinderMatchState();
|
||||
}, er => { });
|
||||
}
|
||||
|
||||
public findPrevious(): void {
|
||||
this._input.data.findPrevious().then(p => {
|
||||
this._profilerTable.setActiveCell(p.row, p.col);
|
||||
this._updateFinderMatchState();
|
||||
}, er => { });
|
||||
}
|
||||
|
||||
public getConfiguration() {
|
||||
return {
|
||||
layoutInfo: {
|
||||
width: this._currentDimensions ? this._currentDimensions.width : 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public layoutOverlayWidget(widget: IOverlayWidget): void {
|
||||
// no op
|
||||
}
|
||||
|
||||
public addOverlayWidget(widget: IOverlayWidget): void {
|
||||
let domNode = widget.getDomNode();
|
||||
domNode.style.right = '28px';
|
||||
this._overlay.appendChild(widget.getDomNode());
|
||||
this._findState.change({ isRevealed: false }, false);
|
||||
}
|
||||
|
||||
public getAction(id: string): IEditorAction {
|
||||
return this._actionMap[id];
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this._profilerTable.focus();
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
this._currentDimensions = dimension;
|
||||
this._profilerTable.layout(dimension);
|
||||
this._onDidChangeConfiguration.fire({ layoutInfo: true });
|
||||
}
|
||||
|
||||
public onSelectedRowsChanged(fn: (e: Slick.EventData, args: Slick.OnSelectedRowsChangedEventArgs<Slick.SlickData>) => any): void {
|
||||
if (this._profilerTable) {
|
||||
this._profilerTable.onSelectedRowsChanged(fn);
|
||||
}
|
||||
}
|
||||
|
||||
private _onStateChange(e: IProfilerStateChangedEvent): void {
|
||||
if (e.autoscroll) {
|
||||
this._profilerTable.autoScroll = this._input.state.autoscroll;
|
||||
}
|
||||
}
|
||||
|
||||
public updateState(): void {
|
||||
this._onStateChange({ autoscroll: true });
|
||||
}
|
||||
|
||||
private _onFindStateChange(e: FindReplaceStateChangedEvent): void {
|
||||
if (e.isRevealed) {
|
||||
if (this._findState.isRevealed) {
|
||||
this._finder.getDomNode().style.top = '0px';
|
||||
this._updateFinderMatchState();
|
||||
} else {
|
||||
this._finder.getDomNode().style.top = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (e.searchString) {
|
||||
if (this._input && this._input.data) {
|
||||
if (this._findState.searchString) {
|
||||
this._input.data.find(this._findState.searchString).then(p => {
|
||||
if (p) {
|
||||
this._profilerTable.setActiveCell(p.row, p.col);
|
||||
this._updateFinderMatchState();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this._input.data.clearFind();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _updateFinderMatchState(): void {
|
||||
if (this._input && this._input.data) {
|
||||
this._findState.changeMatchInfo(this._input.data.findPosition, this._input.data.findCount, undefined);
|
||||
} else {
|
||||
this._findState.changeMatchInfo(0, 0, undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/sql/parts/profiler/editor/interfaces.ts
Normal file
10
src/sql/parts/profiler/editor/interfaces.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
export const CONTEXT_PROFILER_EDITOR = new RawContextKey<boolean>('inProfilerTableEditor', false);
|
||||
|
||||
export const PROFILER_TABLE_COMMAND_SEARCH = 'profiler.table.action.search';
|
||||
452
src/sql/parts/profiler/editor/profilerEditor.ts
Normal file
452
src/sql/parts/profiler/editor/profilerEditor.ts
Normal file
@@ -0,0 +1,452 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ProfilerInput } from './profilerInput';
|
||||
|
||||
import { TabbedPanel } from 'sql/base/browser/ui/panel/panel';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
|
||||
import { IProfilerService, IProfilerSessionTemplate } from 'sql/parts/profiler/service/interfaces';
|
||||
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||
import { attachTableStyler } from 'sql/common/theme/styler';
|
||||
import { IProfilerStateChangedEvent } from './profilerState';
|
||||
import { ProfilerTableEditor } from './controller/profilerTableEditor';
|
||||
import * as Actions from 'sql/parts/profiler/contrib/profilerActions';
|
||||
import { CONTEXT_PROFILER_EDITOR, PROFILER_TABLE_COMMAND_SEARCH } from './interfaces';
|
||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { Dimension, Builder } from 'vs/base/browser/builder';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ProfilerResourceEditor } from './profilerResourceEditor';
|
||||
import { SplitView, View, Orientation, IViewOptions } from 'vs/base/browser/ui/splitview/splitview';
|
||||
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IModel, ICommonCodeEditor } from 'vs/editor/common/editorCommon';
|
||||
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { UNTITLED_SCHEMA } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Command } from 'vs/editor/common/editorCommonExtensions';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { ContextKeyExpr, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { CommonFindController, FindStartFocusAction } from 'vs/editor/contrib/find/common/findController';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
|
||||
|
||||
import { ProfilerTestBackend } from 'sql/parts/profiler/service/profilerTestBackend';
|
||||
|
||||
class BasicView extends View {
|
||||
private _previousSize: number;
|
||||
private _collapsed: boolean;
|
||||
public headerSize: number;
|
||||
constructor(
|
||||
initialSize: number,
|
||||
private _element: HTMLElement,
|
||||
private _focus: () => void,
|
||||
private _layout: (size: number, orientation: Orientation) => void,
|
||||
opts: IViewOptions
|
||||
) {
|
||||
super(initialSize, opts);
|
||||
}
|
||||
|
||||
render(container: HTMLElement, orientation: Orientation): void {
|
||||
container.appendChild(this._element);
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this._focus();
|
||||
}
|
||||
|
||||
layout(size: number, orientation: Orientation): void {
|
||||
if (!this.collapsed) {
|
||||
this._previousSize = size;
|
||||
}
|
||||
this._layout(size, orientation);
|
||||
}
|
||||
|
||||
set collapsed(val: boolean) {
|
||||
this._collapsed = val === false ? false : true;
|
||||
if (this.collapsed) {
|
||||
this._previousSize = this.size;
|
||||
this.setFixed(this.headerSize);
|
||||
} else {
|
||||
this.setFlexible(this._previousSize);
|
||||
}
|
||||
}
|
||||
|
||||
get collapsed(): boolean {
|
||||
return this._collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IDetailData {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class ProfilerEditor extends BaseEditor {
|
||||
public static ID: string = 'workbench.editor.profiler';
|
||||
private _editor: ProfilerResourceEditor;
|
||||
private _editorModel: IModel;
|
||||
private _editorInput: UntitledEditorInput;
|
||||
private _splitView: SplitView;
|
||||
private _container: HTMLElement;
|
||||
private _body: HTMLElement;
|
||||
private _header: HTMLElement;
|
||||
private _actionBar: Taskbar;
|
||||
private _tabbedPanel: TabbedPanel;
|
||||
private _profilerTableEditor: ProfilerTableEditor;
|
||||
private _detailTable: Table<IDetailData>;
|
||||
private _detailTableData: TableDataView<IDetailData>;
|
||||
private _stateListener: IDisposable;
|
||||
private _panelView: BasicView;
|
||||
|
||||
private _profilerEditorContextKey: IContextKey<boolean>;
|
||||
|
||||
private _sessionTemplateSelector: SelectBox;
|
||||
private _sessionTemplates: Array<IProfilerSessionTemplate>;
|
||||
|
||||
// Actions
|
||||
private _connectAction: Actions.ProfilerConnect;
|
||||
private _startAction: Actions.ProfilerStart;
|
||||
private _pauseAction: Actions.ProfilerPause;
|
||||
private _stopAction: Actions.ProfilerStop;
|
||||
private _autoscrollAction: Actions.ProfilerAutoScroll;
|
||||
private _collapsedPanelAction: Actions.ProfilerCollapsablePanelAction;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IWorkbenchThemeService themeService: IWorkbenchThemeService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IContextMenuService private _contextMenuService: IContextMenuService,
|
||||
@IModelService private _modelService: IModelService,
|
||||
@IProfilerService private _profilerService: IProfilerService,
|
||||
@IContextKeyService private _contextKeyService: IContextKeyService,
|
||||
@IContextViewService private _contextViewService: IContextViewService
|
||||
) {
|
||||
super(ProfilerEditor.ID, telemetryService, themeService);
|
||||
this._profilerEditorContextKey = CONTEXT_PROFILER_EDITOR.bindTo(this._contextKeyService);
|
||||
}
|
||||
|
||||
protected createEditor(parent: Builder): void {
|
||||
// test backend
|
||||
this._profilerService.registerProvider('default', this._instantiationService.createInstance(ProfilerTestBackend));
|
||||
|
||||
this._container = document.createElement('div');
|
||||
this._container.className = 'carbon-profiler';
|
||||
parent.append(this._container);
|
||||
|
||||
this._createHeader();
|
||||
|
||||
this._body = document.createElement('div');
|
||||
this._body.className = 'profiler-body';
|
||||
this._container.appendChild(this._body);
|
||||
this._splitView = new SplitView(this._body);
|
||||
|
||||
let tableContainer = this._createProfilerTable();
|
||||
let paneContainer = this._createProfilerPane();
|
||||
this._splitView.addView(new BasicView(
|
||||
undefined,
|
||||
tableContainer,
|
||||
() => this._profilerTableEditor.focus(),
|
||||
size => this._profilerTableEditor.layout(new Dimension(parseFloat(DOM.getComputedStyle(this._body).width), size)),
|
||||
{}
|
||||
));
|
||||
|
||||
this._panelView = new BasicView(
|
||||
undefined,
|
||||
paneContainer,
|
||||
() => this._tabbedPanel.focus(),
|
||||
size => this._tabbedPanel.layout(new Dimension(DOM.getTotalWidth(this._body), size)),
|
||||
{ minimumSize: 35 }
|
||||
);
|
||||
this._panelView.headerSize = 35;
|
||||
this._splitView.addView(this._panelView);
|
||||
}
|
||||
|
||||
private _createHeader(): void {
|
||||
this._header = document.createElement('div');
|
||||
this._header.className = 'profiler-header';
|
||||
this._container.appendChild(this._header);
|
||||
this._actionBar = new Taskbar(this._header, this._contextMenuService);
|
||||
this._startAction = this._instantiationService.createInstance(Actions.ProfilerStart, Actions.ProfilerStart.ID, Actions.ProfilerStart.LABEL);
|
||||
this._startAction.enabled = false;
|
||||
this._stopAction = this._instantiationService.createInstance(Actions.ProfilerStop, Actions.ProfilerStop.ID, Actions.ProfilerStop.LABEL);
|
||||
this._stopAction.enabled = false;
|
||||
this._pauseAction = this._instantiationService.createInstance(Actions.ProfilerPause, Actions.ProfilerPause.ID, Actions.ProfilerPause.LABEL);
|
||||
this._pauseAction.enabled = false;
|
||||
this._connectAction = this._instantiationService.createInstance(Actions.ProfilerConnect, Actions.ProfilerConnect.ID, Actions.ProfilerConnect.LABEL);
|
||||
this._autoscrollAction = this._instantiationService.createInstance(Actions.ProfilerAutoScroll, Actions.ProfilerAutoScroll.ID, Actions.ProfilerAutoScroll.LABEL);
|
||||
|
||||
this._sessionTemplates = this._profilerService.getSessionTemplates();
|
||||
this._sessionTemplateSelector = new SelectBox(this._sessionTemplates.map(i => i.name), 'Standard');
|
||||
this._register(this._sessionTemplateSelector.onDidSelect(e => {
|
||||
if (this.input) {
|
||||
this.input.sessionTemplate = this._sessionTemplates.find(i => i.name === e.selected);
|
||||
}
|
||||
}));
|
||||
let dropdownContainer = document.createElement('div');
|
||||
dropdownContainer.style.width = '150px';
|
||||
this._sessionTemplateSelector.render(dropdownContainer);
|
||||
|
||||
this._register(attachSelectBoxStyler(this._sessionTemplateSelector, this.themeService));
|
||||
|
||||
this._actionBar.setContent([
|
||||
{ action: this._startAction },
|
||||
{ action: this._pauseAction },
|
||||
{ action: this._stopAction },
|
||||
{ action: this._connectAction },
|
||||
{ element: Taskbar.createTaskbarSeparator() },
|
||||
{ action: this._autoscrollAction },
|
||||
{ action: this._instantiationService.createInstance(Actions.ProfilerClear, Actions.ProfilerClear.ID, Actions.ProfilerClear.LABEL) },
|
||||
{ element: dropdownContainer },
|
||||
{ action: this._instantiationService.createInstance(Actions.ProfilerEditColumns, Actions.ProfilerEditColumns.ID, Actions.ProfilerEditColumns.LABEL) }
|
||||
]);
|
||||
}
|
||||
|
||||
private _createProfilerTable(): HTMLElement {
|
||||
let profilerTableContainer = document.createElement('div');
|
||||
profilerTableContainer.className = 'profiler-table monaco-editor';
|
||||
profilerTableContainer.style.width = '100%';
|
||||
profilerTableContainer.style.height = '100%';
|
||||
profilerTableContainer.style.overflow = 'hidden';
|
||||
profilerTableContainer.style.position = 'relative';
|
||||
this._profilerTableEditor = this._instantiationService.createInstance(ProfilerTableEditor);
|
||||
this._profilerTableEditor.createEditor(new Builder(profilerTableContainer));
|
||||
this._profilerTableEditor.onSelectedRowsChanged((e, args) => {
|
||||
let data = this.input.data.getItem(args.rows[0]);
|
||||
if (data) {
|
||||
this._modelService.updateModel(this._editorModel, data['TextData']);
|
||||
this._detailTableData.clear();
|
||||
this._detailTableData.push(Object.keys(data).map(key => {
|
||||
return {
|
||||
label: key,
|
||||
value: data[key]
|
||||
};
|
||||
}));
|
||||
|
||||
if (this.input && types.isUndefinedOrNull(this.input.state.isPanelCollapsed)) {
|
||||
this.input.state.change({ isPanelCollapsed: false });
|
||||
}
|
||||
} else {
|
||||
this._modelService.updateModel(this._editorModel, '');
|
||||
this._detailTableData.clear();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return profilerTableContainer;
|
||||
}
|
||||
|
||||
private _createProfilerPane(): HTMLElement {
|
||||
let editorContainer = this._createProfilerEditor();
|
||||
let tabbedPanelContainer = document.createElement('div');
|
||||
tabbedPanelContainer.className = 'profiler-tabbedPane';
|
||||
this._tabbedPanel = new TabbedPanel(tabbedPanelContainer);
|
||||
this._tabbedPanel.pushTab({
|
||||
identifier: 'editor',
|
||||
title: nls.localize('text', "Text"),
|
||||
view: {
|
||||
layout: dim => this._editor.layout(dim),
|
||||
render: parent => parent.appendChild(editorContainer)
|
||||
}
|
||||
});
|
||||
|
||||
let detailTableContainer = document.createElement('div');
|
||||
detailTableContainer.className = 'profiler-detailTable';
|
||||
detailTableContainer.style.width = '100%';
|
||||
detailTableContainer.style.height = '100%';
|
||||
this._detailTableData = new TableDataView<IDetailData>();
|
||||
this._detailTable = new Table(detailTableContainer, this._detailTableData, [
|
||||
{
|
||||
id: 'label',
|
||||
name: nls.localize('label', "Label"),
|
||||
field: 'label'
|
||||
},
|
||||
{
|
||||
id: 'value',
|
||||
name: nls.localize('value', "Value"),
|
||||
field: 'value'
|
||||
}
|
||||
], { forceFitColumns: true });
|
||||
|
||||
this._tabbedPanel.pushTab({
|
||||
identifier: 'detailTable',
|
||||
title: nls.localize('details', "Details"),
|
||||
view: {
|
||||
layout: dim => this._detailTable.layout(dim),
|
||||
render: parent => parent.appendChild(detailTableContainer)
|
||||
}
|
||||
});
|
||||
|
||||
this._collapsedPanelAction = this._instantiationService.createInstance(Actions.ProfilerCollapsablePanelAction, Actions.ProfilerCollapsablePanelAction.ID, Actions.ProfilerCollapsablePanelAction.LABEL);
|
||||
|
||||
this._tabbedPanel.pushAction(this._collapsedPanelAction, { icon: true, label: false });
|
||||
|
||||
this._register(attachTableStyler(this._detailTable, this.themeService));
|
||||
|
||||
return tabbedPanelContainer;
|
||||
}
|
||||
|
||||
private _createProfilerEditor(): HTMLElement {
|
||||
this._editor = this._instantiationService.createInstance(ProfilerResourceEditor);
|
||||
let editorContainer = document.createElement('div');
|
||||
editorContainer.className = 'profiler-editor';
|
||||
this._editor.create(new Builder(editorContainer));
|
||||
this._editor.setVisible(true);
|
||||
this._editorInput = this._instantiationService.createInstance(UntitledEditorInput, URI.from({ scheme: UNTITLED_SCHEMA }), false, 'sql', '', '');
|
||||
this._editor.setInput(this._editorInput, undefined);
|
||||
this._editorInput.resolve().then(model => this._editorModel = model.textEditorModel);
|
||||
return editorContainer;
|
||||
}
|
||||
|
||||
public get input(): ProfilerInput {
|
||||
return this._input as ProfilerInput;
|
||||
}
|
||||
|
||||
public setInput(input: ProfilerInput, options?: EditorOptions): TPromise<void> {
|
||||
this._profilerEditorContextKey.set(true);
|
||||
if (input instanceof ProfilerInput && input.matches(this.input)) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
return super.setInput(input, options).then(() => {
|
||||
this._profilerTableEditor.setInput(input);
|
||||
|
||||
if (input.sessionTemplate) {
|
||||
this._sessionTemplateSelector.selectWithOptionName(input.sessionTemplate.name);
|
||||
} else {
|
||||
input.sessionTemplate = this._sessionTemplates.find(i => i.name === 'Standard');
|
||||
}
|
||||
|
||||
this._actionBar.context = input;
|
||||
this._tabbedPanel.actionBarContext = input;
|
||||
if (this._stateListener) {
|
||||
this._stateListener.dispose();
|
||||
}
|
||||
this._stateListener = input.state.addChangeListener(e => this._onStateChange(e));
|
||||
this._onStateChange({
|
||||
isConnected: true,
|
||||
isRunning: true,
|
||||
isPaused: true,
|
||||
isStopped: true,
|
||||
autoscroll: true,
|
||||
isPanelCollapsed: true
|
||||
});
|
||||
this._profilerTableEditor.updateState();
|
||||
this._splitView.layout();
|
||||
this._profilerTableEditor.focus();
|
||||
});
|
||||
}
|
||||
|
||||
public clearInput(): void {
|
||||
this._profilerEditorContextKey.set(false);
|
||||
}
|
||||
|
||||
public toggleSearch(): void {
|
||||
if (this._editor.getControl().isFocused()) {
|
||||
let editor = this._editor.getControl() as ICommonCodeEditor;
|
||||
let controller = CommonFindController.get(editor);
|
||||
if (controller) {
|
||||
controller.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: (controller.getState().searchString.length === 0),
|
||||
shouldFocus: FindStartFocusAction.FocusFindInput,
|
||||
shouldAnimate: true
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this._profilerTableEditor.toggleSearch();
|
||||
}
|
||||
}
|
||||
|
||||
private _onStateChange(e: IProfilerStateChangedEvent): void {
|
||||
if (e.autoscroll) {
|
||||
this._autoscrollAction.checked = this.input.state.autoscroll;
|
||||
}
|
||||
|
||||
if (e.isPanelCollapsed) {
|
||||
this._collapsedPanelAction.collapsed = this.input.state.isPanelCollapsed;
|
||||
this._tabbedPanel.collapsed = this.input.state.isPanelCollapsed;
|
||||
this._panelView.collapsed = this.input.state.isPanelCollapsed;
|
||||
}
|
||||
|
||||
if (e.isConnected) {
|
||||
this._connectAction.connected = this.input.state.isConnected;
|
||||
this._startAction.enabled = this.input.state.isConnected;
|
||||
this._stopAction.enabled = false;
|
||||
this._pauseAction.enabled = false;
|
||||
|
||||
if (this.input.state.isConnected) {
|
||||
this._sessionTemplateSelector.disable();
|
||||
} else {
|
||||
this._sessionTemplateSelector.enable();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.isRunning) {
|
||||
this._startAction.enabled = !this.input.state.isRunning;
|
||||
}
|
||||
|
||||
if (e.isStopped || e.isRunning) {
|
||||
this._stopAction.enabled = !this.input.state.isStopped && this.input.state.isRunning;
|
||||
}
|
||||
|
||||
if (e.isPaused || e.isRunning) {
|
||||
this._pauseAction.enabled = !this.input.state.isPaused && this.input.state.isRunning;
|
||||
}
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
this._container.style.width = dimension.width + 'px';
|
||||
this._container.style.height = dimension.height + 'px';
|
||||
this._body.style.width = dimension.width + 'px';
|
||||
this._body.style.height = (dimension.height - (28 + 4)) + 'px';
|
||||
this._splitView.layout(dimension.height - (28 + 4));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class SettingsCommand extends Command {
|
||||
|
||||
protected getProfilerEditor(accessor: ServicesAccessor): ProfilerEditor {
|
||||
const activeEditor = accessor.get(IWorkbenchEditorService).getActiveEditor();
|
||||
if (activeEditor instanceof ProfilerEditor) {
|
||||
return activeEditor;
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class StartSearchProfilerTableCommand extends SettingsCommand {
|
||||
|
||||
public runCommand(accessor: ServicesAccessor, args: any): void {
|
||||
const preferencesEditor = this.getProfilerEditor(accessor);
|
||||
if (preferencesEditor) {
|
||||
preferencesEditor.toggleSearch();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const command = new StartSearchProfilerTableCommand({
|
||||
id: PROFILER_TABLE_COMMAND_SEARCH,
|
||||
precondition: ContextKeyExpr.and(CONTEXT_PROFILER_EDITOR),
|
||||
kbOpts: { primary: KeyMod.CtrlCmd | KeyCode.KEY_F }
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule(command.toCommandAndKeybindingRule(KeybindingsRegistry.WEIGHT.editorContrib()));
|
||||
136
src/sql/parts/profiler/editor/profilerInput.ts
Normal file
136
src/sql/parts/profiler/editor/profilerInput.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
|
||||
import { IProfilerSession, IProfilerService, ProfilerSessionID, IProfilerSessionTemplate } from 'sql/parts/profiler/service/interfaces';
|
||||
import { ProfilerState } from './profilerState';
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
|
||||
import * as data from 'data';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { EditorInput } from 'vs/workbench/common/editor';
|
||||
import { IEditorModel } from 'vs/platform/editor/common/editor';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
export class ProfilerInput extends EditorInput implements IProfilerSession {
|
||||
|
||||
public static ID: string = 'workbench.editorinputs.profilerinputs';
|
||||
public static SCHEMA: string = 'profiler';
|
||||
private _data: TableDataView<Slick.SlickData>;
|
||||
private _id: ProfilerSessionID;
|
||||
private _state: ProfilerState;
|
||||
private _columns: string[] = [];
|
||||
private _sessionTemplate: IProfilerSessionTemplate;
|
||||
|
||||
private _onColumnsChanged = new Emitter<Slick.Column<Slick.SlickData>[]>();
|
||||
public onColumnsChanged: Event<Slick.Column<Slick.SlickData>[]> = this._onColumnsChanged.event;
|
||||
|
||||
constructor(
|
||||
private _connection: IConnectionProfile,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IProfilerService private _profilerService: IProfilerService
|
||||
) {
|
||||
super();
|
||||
this._state = new ProfilerState();
|
||||
// set inital state
|
||||
this.state.change({
|
||||
isConnected: false,
|
||||
isStopped: false,
|
||||
isPaused: false,
|
||||
isRunning: false,
|
||||
autoscroll: true
|
||||
});
|
||||
this._id = this._profilerService.registerSession(generateUuid(), this);
|
||||
let searchFn = (val: { [x: string]: string }, exp: string): Array<number> => {
|
||||
let ret = new Array<number>();
|
||||
for (let i = 0; i < this._columns.length; i++) {
|
||||
if (val[this._columns[i]].includes(exp)) {
|
||||
ret.push(i);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
this._data = new TableDataView<Slick.SlickData>(undefined, searchFn);
|
||||
}
|
||||
|
||||
public set sessionTemplate(template: IProfilerSessionTemplate) {
|
||||
if (!this.state.isConnected || this.state.isStopped) {
|
||||
this._sessionTemplate = template;
|
||||
let newColumns = this.sessionTemplate.view.events.reduce<Array<string>>((p, e) => {
|
||||
e.columns.forEach(c => {
|
||||
if (!p.includes(c)) {
|
||||
p.push(c);
|
||||
}
|
||||
});
|
||||
return p;
|
||||
}, []);
|
||||
newColumns.unshift('EventClass');
|
||||
this.setColumns(newColumns);
|
||||
}
|
||||
}
|
||||
|
||||
public get sessionTemplate(): IProfilerSessionTemplate {
|
||||
return this._sessionTemplate;
|
||||
}
|
||||
|
||||
public getTypeId(): string {
|
||||
return ProfilerInput.ID;
|
||||
}
|
||||
|
||||
public resolve(refresh?: boolean): TPromise<IEditorModel> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public getName(): string {
|
||||
return nls.localize('profiler', 'Profiler');
|
||||
}
|
||||
|
||||
public get data(): TableDataView<Slick.SlickData> {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
public get columns(): Slick.Column<Slick.SlickData>[] {
|
||||
if (this._columns) {
|
||||
return this._columns.map(i => {
|
||||
return <Slick.Column<Slick.SlickData>>{
|
||||
id: i,
|
||||
field: i,
|
||||
name: i,
|
||||
sortable: true
|
||||
};
|
||||
});
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public setColumns(columns: Array<string>) {
|
||||
this._columns = columns;
|
||||
this._onColumnsChanged.fire(this.columns);
|
||||
}
|
||||
|
||||
public get id(): ProfilerSessionID {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public get state(): ProfilerState {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
public onMoreRows(rowCount: number, data: data.IProfilerTableRow) {
|
||||
let validColumns = this.sessionTemplate.view.events.find(i => i.name === data.EventClass).columns;
|
||||
Object.keys(rowCount).forEach(k => {
|
||||
if (!validColumns.includes(k)) {
|
||||
delete rowCount[k];
|
||||
}
|
||||
});
|
||||
this._data.push(data);
|
||||
}
|
||||
}
|
||||
96
src/sql/parts/profiler/editor/profilerResourceEditor.ts
Normal file
96
src/sql/parts/profiler/editor/profilerResourceEditor.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Dimension, Builder } from 'vs/base/browser/builder';
|
||||
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
|
||||
import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { CodeEditor } from 'vs/editor/browser/codeEditor';
|
||||
import { IEditorContributionCtor } from 'vs/editor/browser/editorBrowser';
|
||||
import { FoldingController } from 'vs/editor/contrib/folding/browser/folding';
|
||||
|
||||
class ProfilerResourceCodeEditor extends CodeEditor {
|
||||
|
||||
protected _getContributions(): IEditorContributionCtor[] {
|
||||
let contributions = super._getContributions();
|
||||
let skipContributions = [FoldingController.prototype];
|
||||
contributions = contributions.filter(c => skipContributions.indexOf(c.prototype) === -1);
|
||||
return contributions;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension of TextResourceEditor that is always readonly rather than only with non UntitledInputs
|
||||
*/
|
||||
export class ProfilerResourceEditor extends BaseTextEditor {
|
||||
|
||||
public static ID = 'profiler.editors.textEditor';
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IModeService modeService: IModeService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService
|
||||
|
||||
) {
|
||||
super(ProfilerResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, modeService, textFileService, editorGroupService);
|
||||
}
|
||||
|
||||
public createEditorControl(parent: Builder, configuration: IEditorOptions): editorCommon.IEditor {
|
||||
return this.instantiationService.createInstance(ProfilerResourceCodeEditor, parent.getHTMLElement(), configuration);
|
||||
}
|
||||
|
||||
protected getConfigurationOverrides(): IEditorOptions {
|
||||
const options = super.getConfigurationOverrides();
|
||||
options.readOnly = true;
|
||||
if (this.input) {
|
||||
options.inDiffEditor = true;
|
||||
options.scrollBeyondLastLine = false;
|
||||
options.folding = false;
|
||||
options.renderWhitespace = 'none';
|
||||
options.wordWrap = 'on';
|
||||
options.renderIndentGuides = false;
|
||||
options.rulers = [];
|
||||
options.glyphMargin = true;
|
||||
options.minimap = {
|
||||
enabled: false
|
||||
};
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
setInput(input: UntitledEditorInput, options: EditorOptions): TPromise<void> {
|
||||
return super.setInput(input, options)
|
||||
.then(() => this.input.resolve()
|
||||
.then(editorModel => editorModel.load())
|
||||
.then(editorModel => this.getControl().setModel((<ResourceEditorModel>editorModel).textEditorModel)));
|
||||
}
|
||||
|
||||
protected getAriaLabel(): string {
|
||||
return nls.localize('profilerTextEditorAriaLabel', 'Profiler editor for event text. Readonly');
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension) {
|
||||
this.getControl().layout(dimension);
|
||||
}
|
||||
}
|
||||
116
src/sql/parts/profiler/editor/profilerState.ts
Normal file
116
src/sql/parts/profiler/editor/profilerState.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { EventEmitter } from 'vs/base/common/eventEmitter';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface IProfilerStateChangedEvent {
|
||||
isConnected?: boolean;
|
||||
isRunning?: boolean;
|
||||
isPaused?: boolean;
|
||||
isStopped?: boolean;
|
||||
autoscroll?: boolean;
|
||||
isPanelCollapsed?: boolean;
|
||||
}
|
||||
|
||||
export interface INewProfilerState {
|
||||
isConnected?: boolean;
|
||||
isRunning?: boolean;
|
||||
isPaused?: boolean;
|
||||
isStopped?: boolean;
|
||||
autoscroll?: boolean;
|
||||
isPanelCollapsed?: boolean;
|
||||
}
|
||||
|
||||
export class ProfilerState implements IDisposable {
|
||||
private static _CHANGED_EVENT = 'changed';
|
||||
|
||||
private _isConnected: boolean;
|
||||
private _isRunning: boolean;
|
||||
private _isPaused: boolean;
|
||||
private _isStopped: boolean;
|
||||
private _autoscroll: boolean;
|
||||
private _isPanelCollapsed: boolean;
|
||||
private _eventEmitter: EventEmitter;
|
||||
|
||||
public get isConnected(): boolean { return this._isConnected; }
|
||||
public get isRunning(): boolean { return this._isRunning; }
|
||||
public get isPaused(): boolean { return this._isPaused; }
|
||||
public get isStopped(): boolean { return this._isStopped; }
|
||||
public get autoscroll(): boolean { return this._autoscroll; }
|
||||
public get isPanelCollapsed(): boolean { return this._isPanelCollapsed; }
|
||||
|
||||
constructor() {
|
||||
this._eventEmitter = new EventEmitter();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._eventEmitter.dispose();
|
||||
}
|
||||
|
||||
public addChangeListener(listener: (e: IProfilerStateChangedEvent) => void): IDisposable {
|
||||
return this._eventEmitter.addListener(ProfilerState._CHANGED_EVENT, listener);
|
||||
}
|
||||
|
||||
public change(newState: INewProfilerState): void {
|
||||
let changeEvent: IProfilerStateChangedEvent = {
|
||||
isConnected: false,
|
||||
isRunning: false,
|
||||
isPaused: false,
|
||||
isStopped: false,
|
||||
autoscroll: false,
|
||||
isPanelCollapsed: false
|
||||
};
|
||||
let somethingChanged = false;
|
||||
|
||||
if (typeof newState.isConnected !== 'undefined') {
|
||||
if (this._isConnected !== newState.isConnected) {
|
||||
this._isConnected = newState.isConnected;
|
||||
changeEvent.isConnected = true;
|
||||
somethingChanged = true;
|
||||
}
|
||||
}
|
||||
if (typeof newState.isRunning !== 'undefined') {
|
||||
if (this._isRunning !== newState.isRunning) {
|
||||
this._isRunning = newState.isRunning;
|
||||
changeEvent.isRunning = true;
|
||||
somethingChanged = true;
|
||||
}
|
||||
}
|
||||
if (typeof newState.isPaused !== 'undefined') {
|
||||
if (this._isPaused !== newState.isPaused) {
|
||||
this._isPaused = newState.isPaused;
|
||||
changeEvent.isPaused = true;
|
||||
somethingChanged = true;
|
||||
}
|
||||
}
|
||||
if (typeof newState.isStopped !== 'undefined') {
|
||||
if (this._isStopped !== newState.isStopped) {
|
||||
this._isStopped = newState.isStopped;
|
||||
changeEvent.isStopped = true;
|
||||
somethingChanged = true;
|
||||
}
|
||||
}
|
||||
if (typeof newState.autoscroll !== 'undefined') {
|
||||
if (this._autoscroll !== newState.autoscroll) {
|
||||
this._autoscroll = newState.autoscroll;
|
||||
changeEvent.autoscroll = true;
|
||||
somethingChanged = true;
|
||||
}
|
||||
}
|
||||
if (typeof newState.isPanelCollapsed !== 'undefined') {
|
||||
if (this._isPanelCollapsed !== newState.isPanelCollapsed) {
|
||||
this._isPanelCollapsed = newState.isPanelCollapsed;
|
||||
changeEvent.isPanelCollapsed = true;
|
||||
somethingChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (somethingChanged) {
|
||||
this._eventEmitter.emit(ProfilerState._CHANGED_EVENT, changeEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
103
src/sql/parts/profiler/service/interfaces.ts
Normal file
103
src/sql/parts/profiler/service/interfaces.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import * as data from 'data';
|
||||
|
||||
const PROFILER_SERVICE_ID = 'profilerService';
|
||||
export const IProfilerService = createDecorator<IProfilerService>(PROFILER_SERVICE_ID);
|
||||
|
||||
export type ProfilerSessionID = string;
|
||||
|
||||
export const PROFILER_SESSION_TEMPLATE_SETTINGS = 'profiler.sessionTemplates';
|
||||
export const PROFILER_SETTINGS = 'profiler';
|
||||
|
||||
/**
|
||||
* A front end provider for a profiler session
|
||||
*/
|
||||
export interface IProfilerSession {
|
||||
/**
|
||||
* Called by the service when more rows are available to render
|
||||
*/
|
||||
onMoreRows(rowCount: number, data: data.IProfilerTableRow);
|
||||
}
|
||||
|
||||
/**
|
||||
* A Profiler Service that handles session communication between the backends and frontends
|
||||
*/
|
||||
export interface IProfilerService {
|
||||
_serviceBrand: any;
|
||||
/**
|
||||
* Registers a backend provider for profiler session. ex: mssql
|
||||
*/
|
||||
registerProvider(providerId: string, provider: data.IProfilerProvider): void;
|
||||
/**
|
||||
* Registers a session with the service that acts as the UI for a profiler session
|
||||
* @returns An unique id that should be used to make subsequent calls to this service
|
||||
*/
|
||||
registerSession(uri: string, session: IProfilerSession): ProfilerSessionID;
|
||||
/**
|
||||
* Connects the session specified by the id
|
||||
*/
|
||||
connectSession(sessionId: ProfilerSessionID): Thenable<boolean>;
|
||||
/**
|
||||
* Disconnected the session specified by the id
|
||||
*/
|
||||
disconnectSession(sessionId: ProfilerSessionID): Thenable<boolean>;
|
||||
/**
|
||||
* Starts the session specified by the id
|
||||
*/
|
||||
startSession(sessionId: ProfilerSessionID): Thenable<boolean>;
|
||||
/**
|
||||
* Pauses the session specified by the id
|
||||
*/
|
||||
pauseSession(sessionId: ProfilerSessionID): Thenable<boolean>;
|
||||
/**
|
||||
* Stops the session specified by the id
|
||||
*/
|
||||
stopSession(sessionId: ProfilerSessionID): Thenable<boolean>;
|
||||
/**
|
||||
* The method called by the service provider for when more rows are available to render
|
||||
*/
|
||||
onMoreRows(params: data.IProfilerMoreRowsNotificationParams): void;
|
||||
/**
|
||||
* Gets a list of the session templates that are specified in the settings
|
||||
* @param provider An optional string to limit the session template to a specific
|
||||
* @returns An array of session templates that match the provider passed, if passed, and generic ones (no provider specified),
|
||||
* otherwise returns all session templates
|
||||
*/
|
||||
getSessionTemplates(providerId?: string): Array<IProfilerSessionTemplate>;
|
||||
/**
|
||||
* Launches the dialog for editing the view columns of a profiler session template for the given input
|
||||
* @param input input object that contains the necessary information which will be modified based on used input
|
||||
*/
|
||||
launchColumnEditor(input: ProfilerInput): Thenable<void>;
|
||||
}
|
||||
|
||||
export interface IProfilerSettings {
|
||||
sessionTemplates: Array<IProfilerSessionTemplate>;
|
||||
}
|
||||
|
||||
export interface IEventTemplate {
|
||||
name: string;
|
||||
optionalColumns: Array<string>;
|
||||
}
|
||||
|
||||
export interface IEventViewTemplate {
|
||||
name: string;
|
||||
columns: Array<string>;
|
||||
}
|
||||
|
||||
export interface ISessionViewTemplate {
|
||||
events: Array<IEventViewTemplate>;
|
||||
}
|
||||
|
||||
export interface IProfilerSessionTemplate {
|
||||
name: string;
|
||||
events: Array<IEventTemplate>;
|
||||
view: ISessionViewTemplate;
|
||||
}
|
||||
125
src/sql/parts/profiler/service/profilerService.ts
Normal file
125
src/sql/parts/profiler/service/profilerService.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import {
|
||||
ProfilerSessionID, IProfilerSession, IProfilerService, IProfilerSessionTemplate,
|
||||
PROFILER_SETTINGS, IProfilerSettings
|
||||
} from './interfaces';
|
||||
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
|
||||
import { ProfilerColumnEditorDialog } from 'sql/parts/profiler/dialog/profilerColumnEditorDialog';
|
||||
|
||||
import * as data from 'data';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
class TwoWayMap<T, K> {
|
||||
private forwardMap: Map<T, K>;
|
||||
private reverseMap: Map<K, T>;
|
||||
|
||||
constructor() {
|
||||
this.forwardMap = new Map<T, K>();
|
||||
this.reverseMap = new Map<K, T>();
|
||||
}
|
||||
|
||||
get(input: T): K {
|
||||
return this.forwardMap.get(input);
|
||||
}
|
||||
|
||||
reverseGet(input: K): T {
|
||||
return this.reverseMap.get(input);
|
||||
}
|
||||
|
||||
set(input: T, input2: K): TwoWayMap<T, K> {
|
||||
this.forwardMap.set(input, input2);
|
||||
this.reverseMap.set(input2, input);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export class ProfilerService implements IProfilerService {
|
||||
public _serviceBrand: any;
|
||||
private _providers = new Map<string, data.IProfilerProvider>();
|
||||
private _idMap = new TwoWayMap<ProfilerSessionID, string>();
|
||||
private _sessionMap = new Map<ProfilerSessionID, IProfilerSession>();
|
||||
private _dialog: ProfilerColumnEditorDialog;
|
||||
|
||||
constructor(
|
||||
@IConnectionManagementService private _connectionService: IConnectionManagementService,
|
||||
@IConfigurationService public _configurationService: IConfigurationService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService
|
||||
) { }
|
||||
|
||||
public registerProvider(providerId: string, provider: data.IProfilerProvider): void {
|
||||
this._providers.set(providerId, provider);
|
||||
}
|
||||
|
||||
public registerSession(uri: string, session: IProfilerSession): ProfilerSessionID {
|
||||
this._sessionMap.set(uri, session);
|
||||
this._idMap.set(uri, uri);
|
||||
return uri;
|
||||
}
|
||||
|
||||
public onMoreRows(params: data.IProfilerMoreRowsNotificationParams): void {
|
||||
this._sessionMap.get(this._idMap.reverseGet(params.uri)).onMoreRows(params.rowCount, params.data);
|
||||
}
|
||||
|
||||
public connectSession(id: ProfilerSessionID): Thenable<boolean> {
|
||||
return this._runAction(id, provider => provider.connectSession(this._idMap.get(id)));
|
||||
}
|
||||
|
||||
public disconnectSession(id: ProfilerSessionID): Thenable<boolean> {
|
||||
return this._runAction(id, provider => provider.disconnectSession(this._idMap.get(id)));
|
||||
}
|
||||
|
||||
public startSession(id: ProfilerSessionID): Thenable<boolean> {
|
||||
return this._runAction(id, provider => provider.startSession(this._idMap.get(id)));
|
||||
}
|
||||
|
||||
public pauseSession(id: ProfilerSessionID): Thenable<boolean> {
|
||||
return this._runAction(id, provider => provider.pauseSession(this._idMap.get(id)));
|
||||
}
|
||||
|
||||
public stopSession(id: ProfilerSessionID): Thenable<boolean> {
|
||||
return this._runAction(id, provider => provider.stopSession(this._idMap.get(id)));
|
||||
}
|
||||
|
||||
private _runAction<T>(id: ProfilerSessionID, action: (handler: data.IProfilerProvider) => Thenable<T>): Thenable<T> {
|
||||
// let providerId = this._connectionService.getProviderIdFromUri(this._idMap.get(id));
|
||||
let providerId = 'default';
|
||||
|
||||
if (!providerId) {
|
||||
return TPromise.wrapError(new Error('Connection is required in order to interact with queries'));
|
||||
}
|
||||
let handler = this._providers.get(providerId);
|
||||
if (handler) {
|
||||
return action(handler);
|
||||
} else {
|
||||
return TPromise.wrapError(new Error('No Handler Registered'));
|
||||
}
|
||||
}
|
||||
|
||||
public getSessionTemplates(provider?: string): Array<IProfilerSessionTemplate> {
|
||||
let config = <IProfilerSettings>this._configurationService.getConfiguration(PROFILER_SETTINGS);
|
||||
|
||||
if (provider) {
|
||||
return config.sessionTemplates;
|
||||
} else {
|
||||
return config.sessionTemplates;
|
||||
}
|
||||
}
|
||||
|
||||
public launchColumnEditor(input?: ProfilerInput): Thenable<void> {
|
||||
if (!this._dialog) {
|
||||
this._dialog = this._instantiationService.createInstance(ProfilerColumnEditorDialog);
|
||||
this._dialog.render();
|
||||
}
|
||||
|
||||
this._dialog.open(input);
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
100
src/sql/parts/profiler/service/profilerTestBackend.ts
Normal file
100
src/sql/parts/profiler/service/profilerTestBackend.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IProfilerService } from './interfaces';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import * as path from 'path';
|
||||
|
||||
import * as data from 'data';
|
||||
|
||||
declare var __dirname;
|
||||
|
||||
const columns = [
|
||||
'EventClass',
|
||||
'TextData',
|
||||
'ApplicationName',
|
||||
'NTUserName',
|
||||
'LoginName',
|
||||
'CPU',
|
||||
'Reads',
|
||||
'Writes',
|
||||
'Duration',
|
||||
'ClientProcessID',
|
||||
'SPID',
|
||||
'StartTime',
|
||||
'EndTime',
|
||||
'BinaryData'
|
||||
];
|
||||
|
||||
export class ProfilerTestBackend implements data.IProfilerProvider {
|
||||
private index = 0;
|
||||
private timeOutMap = new Map<string, number>();
|
||||
private testData: Array<Array<string>> = new Array<Array<string>>();
|
||||
|
||||
constructor(
|
||||
@IProfilerService private _profilerService: IProfilerService) { }
|
||||
|
||||
startSession(guid: string): Thenable<boolean> {
|
||||
this.timeOutMap.set(guid, this.intervalFn(guid));
|
||||
return TPromise.as(true);
|
||||
}
|
||||
|
||||
private intervalFn(guid: string): number {
|
||||
return setTimeout(() => {
|
||||
let data = this.testData[this.index++];
|
||||
let formattedData = {
|
||||
EventClass: data[0].trim()
|
||||
};
|
||||
|
||||
for (let i = 1; i < data.length; i++) {
|
||||
formattedData[columns[i]] = data[i];
|
||||
}
|
||||
|
||||
this._profilerService.onMoreRows({ uri: guid, rowCount: 1, data: formattedData });
|
||||
if (this.index >= this.testData.length) {
|
||||
this.index = 0;
|
||||
}
|
||||
this.timeOutMap.set(guid, this.intervalFn(guid));
|
||||
}, Math.floor(Math.random() * 1000) + 300);
|
||||
}
|
||||
|
||||
stopSession(guid: string): Thenable<boolean> {
|
||||
clearTimeout(this.timeOutMap.get(guid));
|
||||
this.index = 0;
|
||||
return TPromise.as(true);
|
||||
}
|
||||
|
||||
pauseSession(guid: string): Thenable<boolean> {
|
||||
clearTimeout(this.timeOutMap.get(guid));
|
||||
return TPromise.as(true);
|
||||
}
|
||||
|
||||
connectSession(): Thenable<boolean> {
|
||||
if (this.testData.length === 0) {
|
||||
return new TPromise<boolean>((resolve, reject) => {
|
||||
pfs.readFile(path.join(__dirname, 'testData.tsv')).then(result => {
|
||||
let tabsep = result.toString().split('\t');
|
||||
for (let i = 0; i < tabsep.length; i++) {
|
||||
if (i % columns.length === 0) {
|
||||
this.testData[i / columns.length] = new Array<string>();
|
||||
}
|
||||
this.testData[Math.floor(i / columns.length)][i % columns.length] = tabsep[i];
|
||||
};
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
|
||||
disconnectSession(guid: string): Thenable<boolean> {
|
||||
clearTimeout(this.timeOutMap.get(guid));
|
||||
this.index = 0;
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
3320
src/sql/parts/profiler/service/testData.tsv
Normal file
3320
src/sql/parts/profiler/service/testData.tsv
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user