move code from parts to contrib (#8319)

This commit is contained in:
Anthony Dresser
2019-11-14 12:23:11 -08:00
committed by GitHub
parent 6438967202
commit 7a2c30e159
619 changed files with 848 additions and 848 deletions

View File

@@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.065 13H15v2H2.056v-2h5.009zm3.661-12H7.385L8.44 2.061 7.505 3H15V1h-4.274zM3.237 9H2.056v2H15V9H3.237zm4.208-4l.995 1-.995 1H15V5H7.445z" fill="#424242"/><path d="M5.072 4.03L7.032 6 5.978 7.061l-1.96-1.97-1.961 1.97L1 6l1.96-1.97L1 2.061 2.056 1l1.96 1.97L5.977 1l1.057 1.061L5.072 4.03z" fill="#A1260D"/></svg>

After

Width:  |  Height:  |  Size: 419 B

View File

@@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.065 13H15v2H2.056v-2h5.009zm3.661-12H7.385L8.44 2.061 7.505 3H15V1h-4.274zM3.237 9H2.056v2H15V9H3.237zm4.208-4l.995 1-.995 1H15V5H7.445z" fill="#C5C5C5"/><path d="M5.072 4.03L7.032 6 5.978 7.061l-1.96-1.97-1.961 1.97L1 6l1.96-1.97L1 2.061 2.056 1l1.96 1.97L5.977 1l1.057 1.061L5.072 4.03z" fill="#F48771"/></svg>

After

Width:  |  Height:  |  Size: 419 B

View File

@@ -0,0 +1,21 @@
/*---------------------------------------------------------------------------------------------
* 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;
}
.vs .codicon.clear-results {
background-image: url('clear.svg');
}
.vs-dark .codicon.clear-results {
background-image: url('clear_inverse.svg');
}

View File

@@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.profiler-filter-dialog {
height: 300px;
padding: 10px;
}
.profiler-filter-clause-table {
width: 100%;
margin-bottom: 10px;
}
.clause-table-container{
max-height: 270px;
overflow-y: scroll;
}
.profiler-filter-remove-condition {
width:20px;
height:20px;
cursor: pointer;
}
.profiler-filter-clause-table-action {
cursor: pointer;
margin-right: 10px;
padding: 2px;
text-decoration: underline;
display: inline-block;
}

View File

@@ -0,0 +1,365 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { Registry } from 'vs/platform/registry/common/platform';
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/workbench/contrib/profiler/browser/profilerInput';
import { ProfilerEditor } from 'sql/workbench/contrib/profiler/browser/profilerEditor';
import { PROFILER_VIEW_TEMPLATE_SETTINGS, PROFILER_SESSION_TEMPLATE_SETTINGS, IProfilerViewTemplate, IProfilerSessionTemplate, EngineType, PROFILER_FILTER_SETTINGS } from 'sql/workbench/services/profiler/browser/interfaces';
const profilerDescriptor = new EditorDescriptor(
ProfilerEditor,
ProfilerEditor.ID,
'ProfilerEditor'
);
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(profilerDescriptor, [new SyncDescriptor(ProfilerInput)]);
const profilerViewTemplateSchema: IJSONSchema = {
description: nls.localize('profiler.settings.viewTemplates', "Specifies view templates"),
type: 'array',
items: <IJSONSchema>{
type: 'object',
properties: {
name: {
type: 'string'
}
}
},
default: <Array<IProfilerViewTemplate>>[
{
name: 'Standard View',
columns: [
{
name: 'EventClass',
eventsMapped: ['name']
},
{
name: 'TextData',
eventsMapped: ['options_text', 'batch_text', 'statement']
},
{
name: 'ApplicationName',
width: '1',
eventsMapped: ['client_app_name']
},
{
name: 'NTUserName',
eventsMapped: ['nt_username']
},
{
name: 'LoginName',
eventsMapped: ['server_principal_name']
},
{
name: 'ClientProcessID',
eventsMapped: ['client_pid']
},
{
name: 'SPID',
eventsMapped: ['session_id']
},
{
name: 'StartTime',
eventsMapped: ['timestamp']
},
{
name: 'CPU',
eventsMapped: ['cpu_time']
},
{
name: 'Reads',
eventsMapped: ['logical_reads']
},
{
name: 'Writes',
eventsMapped: ['writes']
},
{
name: 'Duration',
eventsMapped: ['duration']
},
{
name: 'DatabaseID',
eventsMapped: ['database_id']
},
{
name: 'DatabaseName',
eventsMapped: ['database_name']
}
]
},
{
name: 'TSQL View',
columns: [
{
name: 'EventClass',
eventsMapped: ['name']
},
{
name: 'TextData',
eventsMapped: ['options_text', 'batch_text', 'statement']
},
{
name: 'SPID',
eventsMapped: ['session_id']
},
{
name: 'StartTime',
eventsMapped: ['timestamp']
},
{
name: 'DatabaseID',
eventsMapped: ['database_id']
},
{
name: 'DatabaseName',
eventsMapped: ['database_name']
}
]
},
{
name: 'Tuning View',
columns: [
{
name: 'EventClass',
eventsMapped: ['name']
},
{
name: 'TextData',
eventsMapped: ['options_text', 'batch_text', 'statement']
},
{
name: 'Duration',
eventsMapped: ['duration']
},
{
name: 'SPID',
eventsMapped: ['session_id']
},
{
name: 'DatabaseID',
eventsMapped: ['database_id']
},
{
name: 'DatabaseName',
eventsMapped: ['database_name']
},
{
name: 'ObjectType',
eventsMapped: ['object_type']
},
{
name: 'LoginName',
eventsMapped: ['server_principal_name']
}
]
},
{
name: 'TSQL_Locks View',
columns: [
{
name: 'EventClass',
eventsMapped: ['name']
},
{
name: 'TextData',
eventsMapped: ['options_text', 'batch_text', 'statement']
},
{
name: 'ApplicationName',
eventsMapped: ['client_app_name']
},
{
name: 'NTUserName',
eventsMapped: ['nt_username']
},
{
name: 'LoginName',
eventsMapped: ['server_principal_name']
},
{
name: 'ClientProcessID',
eventsMapped: ['client_pid']
},
{
name: 'SPID',
eventsMapped: ['session_id']
},
{
name: 'StartTime',
eventsMapped: ['timestamp']
},
{
name: 'CPU',
eventsMapped: ['cpu_time']
},
{
name: 'Reads',
eventsMapped: ['logical_reads']
},
{
name: 'Writes',
eventsMapped: ['writes']
},
{
name: 'Duration',
eventsMapped: ['duration']
},
{
name: 'DatabaseID',
eventsMapped: ['database_id']
},
{
name: 'DatabaseName',
eventsMapped: ['database_name']
}
]
},
{
name: 'TSQL_Duration View',
columns: [
{
name: 'EventClass',
eventsMapped: ['name']
},
{
name: 'Duration',
eventsMapped: ['duration']
},
{
name: 'TextData',
eventsMapped: ['options_text', 'batch_text', 'statement']
},
{
name: 'SPID',
eventsMapped: ['session_id']
},
{
name: 'DatabaseID',
eventsMapped: ['database_id']
},
{
name: 'DatabaseName',
eventsMapped: ['database_name']
}
]
}
]
};
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_OnPrem',
defaultView: 'Standard View',
engineTypes: [EngineType.Standalone],
createStatement:
`CREATE EVENT SESSION [{sessionName}] ON SERVER
ADD EVENT sqlserver.attention(
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id)
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.existing_connection(SET collect_options_text=(1)
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.nt_username,sqlserver.server_principal_name,sqlserver.session_id)),
ADD EVENT sqlserver.login(SET collect_options_text=(1)
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.nt_username,sqlserver.server_principal_name,sqlserver.session_id)),
ADD EVENT sqlserver.logout(
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.nt_username,sqlserver.server_principal_name,sqlserver.session_id)),
ADD EVENT sqlserver.rpc_completed(
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.database_name,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id)
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.sql_batch_completed(
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.database_name,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id)
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.sql_batch_starting(
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.database_name,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id)
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0))))
ADD TARGET package0.ring_buffer(SET max_events_limit=(1000),max_memory=(51200))
WITH (MAX_MEMORY=8192 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=5 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=PER_CPU,TRACK_CAUSALITY=ON,STARTUP_STATE=OFF)`
},
{
name: 'Standard_Azure',
engineTypes: [EngineType.AzureSQLDB],
defaultView: 'Standard View',
createStatement:
`CREATE EVENT SESSION [{sessionName}] ON DATABASE
ADD EVENT sqlserver.attention(
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.username,sqlserver.query_hash,sqlserver.session_id)
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.existing_connection(SET collect_options_text=(1)
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.username,sqlserver.session_id)),
ADD EVENT sqlserver.login(SET collect_options_text=(1)
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.username,sqlserver.session_id)),
ADD EVENT sqlserver.logout(
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.username,sqlserver.session_id)),
ADD EVENT sqlserver.rpc_completed(
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.username,sqlserver.query_hash,sqlserver.session_id)
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.sql_batch_completed(
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.username,sqlserver.query_hash,sqlserver.session_id)
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.sql_batch_starting(
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.username,sqlserver.query_hash,sqlserver.session_id)
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0))))
ADD TARGET package0.ring_buffer(SET max_events_limit=(1000),max_memory=(51200))
WITH (MAX_MEMORY=8192 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=5 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=PER_CPU,TRACK_CAUSALITY=ON,STARTUP_STATE=OFF)`
},
{
name: 'TSQL_OnPrem',
engineTypes: [EngineType.Standalone],
defaultView: 'TSQL View',
createStatement:
`CREATE EVENT SESSION [{sessionName}] ON SERVER
ADD EVENT sqlserver.existing_connection(
ACTION(package0.event_sequence,sqlserver.session_id,sqlserver.client_hostname)),
ADD EVENT sqlserver.login(SET collect_options_text=(1)
ACTION(package0.event_sequence,sqlserver.session_id,sqlserver.client_hostname)),
ADD EVENT sqlserver.logout(
ACTION(package0.event_sequence,sqlserver.session_id)),
ADD EVENT sqlserver.rpc_starting(
ACTION(package0.event_sequence,sqlserver.session_id,sqlserver.database_id,sqlserver.database_name)
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.sql_batch_starting(
ACTION(package0.event_sequence,sqlserver.session_id,sqlserver.database_id,sqlserver.database_name)
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0))))
ADD TARGET package0.ring_buffer(SET max_events_limit=(1000),max_memory=(51200))
WITH (MAX_MEMORY=8192 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=5 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=PER_CPU,TRACK_CAUSALITY=ON,STARTUP_STATE=OFF)`
}
]
};
const profilerFiltersSchema: IJSONSchema = {
description: nls.localize('profiler.settings.Filters', "Profiler Filters"),
type: 'array'
};
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
const profilerConfig: IConfigurationNode = {
id: 'Profiler',
type: 'object',
properties: {
[PROFILER_VIEW_TEMPLATE_SETTINGS]: profilerViewTemplateSchema,
[PROFILER_SESSION_TEMPLATE_SETTINGS]: profilerSessionTemplateSchema,
[PROFILER_FILTER_SETTINGS]: profilerFiltersSchema
}
};
configurationRegistry.registerConfiguration(profilerConfig);

View File

@@ -0,0 +1,99 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { ProfilerInput } from 'sql/workbench/contrib/profiler/browser/profilerInput';
import * as TaskUtilities from 'sql/workbench/browser/taskUtilities';
import { IProfilerService } from 'sql/workbench/services/profiler/browser/interfaces';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ProfilerEditor } from 'sql/workbench/contrib/profiler/browser/profilerEditor';
import { ObjectExplorerActionsContext } from 'sql/workbench/contrib/objectExplorer/browser/objectExplorerActions';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService';
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
CommandsRegistry.registerCommand({
id: 'profiler.newProfiler',
handler: (accessor: ServicesAccessor, ...args: any[]) => {
let connectionProfile: ConnectionProfile = undefined;
let instantiationService: IInstantiationService = accessor.get(IInstantiationService);
let editorService: IEditorService = accessor.get(IEditorService);
let connectionService: IConnectionManagementService = accessor.get(IConnectionManagementService);
let objectExplorerService: IObjectExplorerService = accessor.get(IObjectExplorerService);
let connectionDialogService: IConnectionDialogService = accessor.get(IConnectionDialogService);
let capabilitiesService: ICapabilitiesService = accessor.get(ICapabilitiesService);
// If a context is available if invoked from the context menu, we will use the connection profiler of the server node
if (args && args.length === 1 && args[0] && args[0] instanceof ObjectExplorerActionsContext) {
let context = args[0] as ObjectExplorerActionsContext;
connectionProfile = ConnectionProfile.fromIConnectionProfile(capabilitiesService, context.connectionProfile);
}
else {
// No context available, we will try to get the current global active connection
connectionProfile = TaskUtilities.getCurrentGlobalConnection(objectExplorerService, connectionService, editorService) as ConnectionProfile;
}
let promise;
if (connectionProfile) {
promise = connectionService.connectIfNotConnected(connectionProfile, 'connection', true);
} else {
// if still no luck, we will open the Connection dialog and let user connect to a server
promise = connectionDialogService.openDialogAndWait(connectionService, { connectionType: 0, showDashboard: false, providers: [mssqlProviderName] }).then((profile) => {
connectionProfile = profile as ConnectionProfile;
});
}
return promise.then(() => {
if (!connectionProfile) {
connectionProfile = TaskUtilities.getCurrentGlobalConnection(objectExplorerService, connectionService, editorService) as ConnectionProfile;
}
if (connectionProfile && connectionProfile.providerName === mssqlProviderName) {
let profilerInput = instantiationService.createInstance(ProfilerInput, connectionProfile);
editorService.openEditor(profilerInput, { pinned: true }, ACTIVE_GROUP).then(() => Promise.resolve(true));
}
});
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'profiler.newProfiler',
weight: KeybindingWeight.BuiltinExtension,
when: undefined,
primary: KeyMod.Alt | KeyCode.KEY_P,
mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KEY_P },
handler: CommandsRegistry.getCommand('profiler.newProfiler').handler
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'profiler.toggleStartStop',
weight: KeybindingWeight.EditorContrib,
when: undefined,
primary: KeyMod.Alt | KeyCode.KEY_S,
mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KEY_S },
handler: (accessor: ServicesAccessor) => {
let profilerService: IProfilerService = accessor.get(IProfilerService);
let editorService: IEditorService = accessor.get(IEditorService);
let activeEditor = editorService.activeControl;
if (activeEditor instanceof ProfilerEditor) {
let profilerInput = activeEditor.input;
if (profilerInput.state.isRunning) {
return profilerService.stopSession(profilerInput.id);
} else {
// clear data when profiler is started
profilerInput.data.clear();
return profilerService.startSession(profilerInput.id, profilerInput.sessionName);
}
}
return Promise.resolve(false);
}
});

View File

@@ -0,0 +1,329 @@
/*---------------------------------------------------------------------------------------------
* 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 'sql/workbench/services/profiler/browser/interfaces';
import { IProfilerController } from 'sql/workbench/contrib/profiler/common/interfaces';
import { ProfilerInput } from 'sql/workbench/contrib/profiler/browser/profilerInput';
import { Task } from 'sql/platform/tasks/browser/tasksRegistry';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { IConnectionManagementService, IConnectionCompletionOptions } from 'sql/platform/connection/common/connectionManagement';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { Action } from 'vs/base/common/actions';
import * as nls from 'vs/nls';
import { IEditorAction } from 'vs/editor/common/editorCommon';
import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
export class ProfilerConnect extends Action {
private static readonly ConnectText = nls.localize('profilerAction.connect', "Connect");
private static readonly DisconnectText = nls.localize('profilerAction.disconnect', "Disconnect");
public static ID = 'profiler.connect';
public static LABEL = ProfilerConnect.ConnectText;
private _connected: boolean = false;
constructor(
id: string, label: string,
@IProfilerService private _profilerService: IProfilerService
) {
super(id, label, 'connect');
}
public run(input: ProfilerInput): Promise<boolean> {
this.enabled = false;
if (!this._connected) {
return Promise.resolve(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 Promise.resolve(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.label = value ? ProfilerConnect.DisconnectText : ProfilerConnect.ConnectText;
}
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, 'sql start');
}
public run(input: ProfilerInput): Promise<boolean> {
input.data.clear();
return Promise.resolve(this._profilerService.startSession(input.id, input.sessionName));
}
}
export class ProfilerCreate extends Action {
public static ID = 'profiler.create';
public static LABEL = nls.localize('create', "New Session");
constructor(
id: string, label: string,
@IProfilerService private _profilerService: IProfilerService
) {
super(id, label, 'add');
}
public run(input: ProfilerInput): Promise<boolean> {
return Promise.resolve(this._profilerService.launchCreateSessionDialog(input).then(() => {
return true;
}));
}
}
export class ProfilerPause extends Action {
private static readonly PauseText = nls.localize('profilerAction.pauseCapture', "Pause");
private static readonly ResumeText = nls.localize('profilerAction.resumeCapture', "Resume");
private static readonly PauseCssClass = 'sql pause';
private static readonly ResumeCssClass = 'sql continue';
public static ID = 'profiler.pause';
public static LABEL = ProfilerPause.PauseText;
private _paused: boolean = false;
constructor(
id: string, label: string,
@IProfilerService private _profilerService: IProfilerService
) {
super(id, label, ProfilerPause.PauseCssClass);
}
public run(input: ProfilerInput): Promise<boolean> {
return Promise.resolve(this._profilerService.pauseSession(input.id).then(() => {
this.paused = !this._paused;
input.state.change({ isPaused: this.paused, isStopped: false, isRunning: !this.paused });
return true;
}));
}
public set paused(value: boolean) {
this._paused = value;
this._setClass(value ? ProfilerPause.ResumeCssClass : ProfilerPause.PauseCssClass);
this.label = value ? ProfilerPause.ResumeText : ProfilerPause.PauseText;
}
public get paused(): boolean {
return this._paused;
}
}
export class ProfilerStop extends Action {
public static ID = 'profiler.stop';
public static LABEL = nls.localize('profilerStop.stop', "Stop");
constructor(
id: string, label: string,
@IProfilerService private _profilerService: IProfilerService
) {
super(id, label, 'sql stop');
}
public run(input: ProfilerInput): Promise<boolean> {
return Promise.resolve(this._profilerService.stopSession(input.id));
}
}
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, 'clear-results');
}
run(input: ProfilerInput): Promise<void> {
input.data.clear();
return Promise.resolve(null);
}
}
export class ProfilerAutoScroll extends Action {
private static readonly AutoScrollOnText = nls.localize('profilerAction.autoscrollOn', "Auto Scroll: On");
private static readonly AutoScrollOffText = nls.localize('profilerAction.autoscrollOff', "Auto Scroll: Off");
private static readonly CheckedCssClass = 'sql checked';
public static ID = 'profiler.autoscroll';
public static LABEL = ProfilerAutoScroll.AutoScrollOnText;
constructor(id: string, label: string) {
super(id, label, ProfilerAutoScroll.CheckedCssClass);
}
run(input: ProfilerInput): Promise<boolean> {
this.checked = !this.checked;
this.label = this.checked ? ProfilerAutoScroll.AutoScrollOnText : ProfilerAutoScroll.AutoScrollOffText;
this._setClass(this.checked ? ProfilerAutoScroll.CheckedCssClass : '');
input.state.change({ autoscroll: this.checked });
return Promise.resolve(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, 'codicon-chevron-down');
}
public run(input: ProfilerInput): Promise<boolean> {
this.collapsed = !this._collapsed;
input.state.change({ isPanelCollapsed: this._collapsed });
return Promise.resolve(true);
}
get collapsed(): boolean {
return this._collapsed;
}
set collapsed(val: boolean) {
this._collapsed = val === false ? false : true;
this._setClass(this._collapsed ? 'codicon-chevron-up' : 'codicon-chevron-down');
}
}
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): Promise<boolean> {
return Promise.resolve(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(): Promise<void> {
this.profiler.findNext();
return Promise.resolve(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(): Promise<void> {
this.profiler.findPrevious();
return Promise.resolve(null);
}
isSupported(): boolean {
return true;
}
}
export class NewProfilerAction extends Task {
public static readonly ID = 'profiler.newProfiler';
public static readonly LABEL = nls.localize('profilerAction.newProfiler', "Launch Profiler");
public static readonly ICON = 'profile';
private _connectionProfile: ConnectionProfile;
constructor() {
super({
id: NewProfilerAction.ID,
title: NewProfilerAction.LABEL,
iconPath: { dark: NewProfilerAction.ICON, light: NewProfilerAction.ICON },
iconClass: NewProfilerAction.ICON
});
}
public runTask(accessor: ServicesAccessor, profile: IConnectionProfile): Promise<void> {
let profilerInput = accessor.get<IInstantiationService>(IInstantiationService).createInstance(ProfilerInput, profile);
return accessor.get<IEditorService>(IEditorService).openEditor(profilerInput, { pinned: true }, ACTIVE_GROUP).then(() => {
let options: IConnectionCompletionOptions = {
params: undefined,
saveTheConnection: false,
showConnectionDialogOnError: true,
showDashboard: false,
showFirewallRuleOnError: true
};
accessor.get<IConnectionManagementService>(IConnectionManagementService).connect(this._connectionProfile, profilerInput.id, options);
return Promise.resolve(void 0);
});
}
}
export class ProfilerFilterSession extends Action {
public static ID = 'profiler.filter';
public static LABEL = nls.localize('profiler.filter', "Filter…");
constructor(
id: string, label: string,
@IProfilerService private _profilerService: IProfilerService
) {
super(id, label, 'filterLabel');
}
public run(input: ProfilerInput): Promise<boolean> {
this._profilerService.launchFilterSessionDialog(input);
return Promise.resolve(true);
}
}
export class ProfilerClearSessionFilter extends Action {
public static ID = 'profiler.clearFilter';
public static LABEL = nls.localize('profiler.clearFilter', "Clear Filter");
constructor(
id: string, label: string
) {
super(id, label, 'clear-filter');
}
public run(input: ProfilerInput): Promise<boolean> {
input.clearFilter();
return Promise.resolve(true);
}
}

View File

@@ -0,0 +1,414 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/profiler';
import { Modal } from 'sql/workbench/browser/modal/modal';
import { attachModalDialogStyler } from 'sql/platform/theme/common/styler';
import { ProfilerInput } from 'sql/workbench/contrib/profiler/browser/profilerInput';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import * as nls from 'vs/nls';
import { IThemeService } from 'vs/platform/theme/common/themeService';
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 { attachListStyler } from 'vs/platform/theme/common/styler';
import { Event, Emitter } from 'vs/base/common/event';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { ILogService } from 'vs/platform/log/common/log';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
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 {
const data = Object.create(null);
const 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): Promise<Array<any>> {
if (element instanceof EventItem) {
return Promise.resolve(element.getChildren());
} else if (element instanceof SessionItem) {
return Promise.resolve(element.getChildren());
} else {
return Promise.resolve(null);
}
}
getParent(tree: ITree, element: any): Promise<any> {
if (element instanceof ColumnItem) {
return Promise.resolve(element.parent);
} else if (element instanceof EventItem) {
return Promise.resolve(element.parent);
} else if (element instanceof ColumnSortedColumnItem) {
return Promise.resolve(element.parent);
} else {
return Promise.resolve(null);
}
}
shouldAutoexpand?(tree: ITree, element: any): boolean {
return false;
}
}
export class ProfilerColumnEditorDialog extends Modal {
private _selectBox: SelectBox;
private readonly _options = [
{ text: nls.localize('eventSort', "Sort by event") },
{ text: nls.localize('nameColumn', "Sort by column") }
];
private _tree: Tree;
private _element: SessionItem;
private _treeContainer: HTMLElement;
constructor(
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@IThemeService themeService: IThemeService,
@IAdsTelemetryService telemetryService: IAdsTelemetryService,
@IContextKeyService contextKeyService: IContextKeyService,
@IContextViewService private _contextViewService: IContextViewService,
@IClipboardService clipboardService: IClipboardService,
@ILogService logService: ILogService,
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService
) {
super(nls.localize('profilerColumnDialog.profiler', "Profiler"), TelemetryKeys.Profiler, telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService);
}
public render(): void {
super.render();
this._register(attachModalDialogStyler(this, this._themeService));
this.addFooterButton(nls.localize('profilerColumnDialog.ok', "OK"), () => this.onAccept(undefined));
this.addFooterButton(nls.localize('profilerColumnDialog.cancel', "Cancel"), () => this.onClose(undefined));
}
protected renderBody(container: HTMLElement): void {
const body = DOM.append(container, DOM.$(''));
this._selectBox = new SelectBox(this._options, 0, this._contextViewService);
this._selectBox.render(body);
this._register(this._selectBox.onDidSelect(e => {
this._element.changeSort(e.index === 0 ? 'event' : 'column');
this._tree.refresh(this._element, true);
}));
this._treeContainer = DOM.append(body, DOM.$('.profiler-column-tree'));
const 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._updateList();
}
protected onAccept(e: StandardKeyboardEvent): void {
this._updateInput();
super.onAccept(e);
}
// currently not used, this dialog is a work in progress
// tracked in issue #1545 https://github.com/Microsoft/azuredatastudio/issues/1545
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);
*/
}
// currently not used, this dialog is a work in progress
// tracked in issue #1545 https://github.com/Microsoft/azuredatastudio/issues/1545
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));
}
}

View File

@@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
export function handleCopyRequest(clipboardService: IClipboardService, textResourcePropertiesService: ITextResourcePropertiesService, range: Slick.Range, getCellValue: (row, cell) => string): void {
if (range) {
let results = '';
for (let i = range.fromRow; i <= range.toRow; i++) {
for (let j = range.fromCell; j <= range.toCell; j++) {
let value = getCellValue(i, j);
if (j !== range.toCell) {
value += '\t';
}
results += value;
}
if (i !== range.toRow) {
results += textResourcePropertiesService.getEOL(URI.from({ scheme: Schemas.untitled }));
}
}
clipboardService.writeText(results);
}
}

View File

@@ -0,0 +1,651 @@
/*---------------------------------------------------------------------------------------------
* 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/workbench/contrib/profiler/browser/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, IProfilerViewTemplate } from 'sql/workbench/services/profiler/browser/interfaces';
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
import { attachTableStyler, attachTabbedPanelStyler } from 'sql/platform/theme/common/styler';
import { IProfilerStateChangedEvent } from 'sql/workbench/contrib/profiler/common/profilerState';
import { ProfilerTableEditor, ProfilerTableViewState } from 'sql/workbench/contrib/profiler/browser/profilerTableEditor';
import * as Actions from 'sql/workbench/contrib/profiler/browser/profilerActions';
import { CONTEXT_PROFILER_EDITOR, PROFILER_TABLE_COMMAND_SEARCH } from 'sql/workbench/contrib/profiler/common/interfaces';
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
import { textFormatter, slickGridDataItemColumnValueExtractor } from 'sql/base/browser/ui/table/formatters';
import { ProfilerResourceEditor } from 'sql/workbench/contrib/profiler/browser/profilerResourceEditor';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { ITextModel } from 'vs/editor/common/model';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
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/browser/editorExtensions';
import { IEditorService } 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 { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { CommonFindController, FindStartFocusAction } from 'vs/editor/contrib/find/findController';
import * as types from 'vs/base/common/types';
import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
import { DARK, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IView, SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview';
import * as DOM from 'vs/base/browser/dom';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { EditorOptions } from 'vs/workbench/common/editor';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkbenchThemeService, VS_DARK_THEME, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { Event, Emitter } from 'vs/base/common/event';
import { clamp } from 'vs/base/common/numbers';
import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugin';
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
import { handleCopyRequest } from 'sql/workbench/contrib/profiler/browser/profilerCopyHandler';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
import { find } from 'vs/base/common/arrays';
class BasicView implements IView {
public get element(): HTMLElement {
return this._element;
}
private _onDidChange = new Emitter<number>();
public readonly onDidChange: Event<number> = this._onDidChange.event;
private _collapsed = false;
private size: number;
private previousSize: number;
private _minimumSize: number;
public get minimumSize(): number {
return this._minimumSize;
}
private _maximumSize: number;
public get maximumSize(): number {
return this._maximumSize;
}
constructor(
private _defaultMinimumSize: number,
private _defaultMaximumSize: number,
private _layout: (size: number) => void,
private _element: HTMLElement,
private options: { headersize?: number } = {}
) {
this._minimumSize = _defaultMinimumSize;
this._maximumSize = _defaultMaximumSize;
}
public layout(size: number): void {
this.size = size;
this._layout(size);
}
public set collapsed(val: boolean) {
if (val !== this._collapsed && this.options.headersize) {
this._collapsed = val;
if (this.collapsed) {
this.previousSize = this.size;
this._minimumSize = this.options.headersize;
this._maximumSize = this.options.headersize;
this._onDidChange.fire(undefined);
} else {
this._maximumSize = this._defaultMaximumSize;
this._minimumSize = this._defaultMinimumSize;
this._onDidChange.fire(clamp(this.previousSize, this.minimumSize, this.maximumSize));
}
}
}
public get collapsed(): boolean {
return this._collapsed;
}
}
export interface IDetailData {
label: string;
value: string;
}
export class ProfilerEditor extends BaseEditor {
public static readonly ID: string = 'workbench.editor.profiler';
private _editor: ProfilerResourceEditor;
private _editorModel: ITextModel;
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 _viewTemplateSelector: SelectBox;
private _viewTemplates: Array<IProfilerViewTemplate>;
private _sessionSelector: SelectBox;
private _sessionsList: Array<string>;
// Actions
private _connectAction: Actions.ProfilerConnect;
private _startAction: Actions.ProfilerStart;
private _pauseAction: Actions.ProfilerPause;
private _stopAction: Actions.ProfilerStop;
private _autoscrollAction: Actions.ProfilerAutoScroll;
private _createAction: Actions.ProfilerCreate;
private _collapsedPanelAction: Actions.ProfilerCollapsablePanelAction;
private _filterAction: Actions.ProfilerFilterSession;
private _clearFilterAction: Actions.ProfilerClearSessionFilter;
private _savedTableViewStates = new Map<ProfilerInput, ProfilerTableViewState>();
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IWorkbenchThemeService themeService: IWorkbenchThemeService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IModelService private _modelService: IModelService,
@IProfilerService private _profilerService: IProfilerService,
@IContextKeyService private _contextKeyService: IContextKeyService,
@IContextViewService private _contextViewService: IContextViewService,
@IEditorService editorService: IEditorService,
@IStorageService storageService: IStorageService,
@IClipboardService private _clipboardService: IClipboardService,
@ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService
) {
super(ProfilerEditor.ID, telemetryService, themeService, storageService);
this._profilerEditorContextKey = CONTEXT_PROFILER_EDITOR.bindTo(this._contextKeyService);
if (editorService) {
editorService.overrideOpenEditor((editor, options, group) => {
if (this.isVisible() && (editor !== this.input || group !== this.group)) {
this.saveEditorViewState();
}
return {};
});
}
}
protected createEditor(parent: HTMLElement): void {
this._container = document.createElement('div');
this._container.className = 'carbon-profiler';
parent.appendChild(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(
300,
Number.POSITIVE_INFINITY,
size => this._profilerTableEditor.layout(new DOM.Dimension(parseFloat(DOM.getComputedStyle(this._body).width), size)),
tableContainer
), Sizing.Distribute);
this._panelView = new BasicView(
300,
Number.POSITIVE_INFINITY,
size => this._tabbedPanel.layout(new DOM.Dimension(DOM.getTotalWidth(this._body), size)),
paneContainer,
{ headersize: 35 }
);
this._splitView.addView(this._panelView, Sizing.Distribute);
}
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._startAction = this._instantiationService.createInstance(Actions.ProfilerStart, Actions.ProfilerStart.ID, Actions.ProfilerStart.LABEL);
this._startAction.enabled = false;
this._createAction = this._instantiationService.createInstance(Actions.ProfilerCreate, Actions.ProfilerCreate.ID, Actions.ProfilerCreate.LABEL);
this._createAction.enabled = true;
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._filterAction = this._instantiationService.createInstance(Actions.ProfilerFilterSession, Actions.ProfilerFilterSession.ID, Actions.ProfilerFilterSession.LABEL);
this._filterAction.enabled = true;
this._clearFilterAction = this._instantiationService.createInstance(Actions.ProfilerClearSessionFilter, Actions.ProfilerClearSessionFilter.ID, Actions.ProfilerClearSessionFilter.LABEL);
this._clearFilterAction.enabled = true;
this._viewTemplates = this._profilerService.getViewTemplates();
this._viewTemplateSelector = new SelectBox(this._viewTemplates.map(i => i.name), 'Standard View', this._contextViewService);
this._viewTemplateSelector.setAriaLabel(nls.localize('profiler.viewSelectAccessibleName', "Select View"));
this._register(this._viewTemplateSelector.onDidSelect(e => {
if (this.input) {
this.input.viewTemplate = find(this._viewTemplates, i => i.name === e.selected);
}
}));
let viewTemplateContainer = document.createElement('div');
viewTemplateContainer.style.width = '150px';
viewTemplateContainer.style.paddingRight = '5px';
this._viewTemplateSelector.render(viewTemplateContainer);
this._sessionsList = [''];
this._sessionSelector = new SelectBox(this._sessionsList, '', this._contextViewService);
this._sessionSelector.setAriaLabel(nls.localize('profiler.sessionSelectAccessibleName', "Select Session"));
this._register(this._sessionSelector.onDidSelect(e => {
if (this.input) {
this.input.sessionName = e.selected;
}
}));
let sessionsContainer = document.createElement('div');
sessionsContainer.style.minWidth = '150px';
sessionsContainer.style.maxWidth = '250px';
sessionsContainer.style.paddingRight = '5px';
this._sessionSelector.render(sessionsContainer);
this._register(attachSelectBoxStyler(this._viewTemplateSelector, this.themeService));
this._register(attachSelectBoxStyler(this._sessionSelector, this.themeService));
this._actionBar.setContent([
{ action: this._createAction },
{ element: Taskbar.createTaskbarSeparator() },
{ element: this._createTextElement(nls.localize('profiler.sessionSelectLabel', "Select Session:")) },
{ element: sessionsContainer },
{ action: this._startAction },
{ action: this._stopAction },
{ action: this._pauseAction },
{ element: Taskbar.createTaskbarSeparator() },
{ action: this._filterAction },
{ action: this._clearFilterAction },
{ element: Taskbar.createTaskbarSeparator() },
{ element: this._createTextElement(nls.localize('profiler.viewSelectLabel', "Select View:")) },
{ element: viewTemplateContainer },
{ action: this._autoscrollAction },
{ action: this._instantiationService.createInstance(Actions.ProfilerClear, Actions.ProfilerClear.ID, Actions.ProfilerClear.LABEL) }
]);
}
private _createTextElement(text: string): HTMLDivElement {
let textElement = document.createElement('div');
textElement.style.paddingRight = '10px';
textElement.innerText = text;
textElement.style.textAlign = 'center';
textElement.style.display = 'flex';
textElement.style.alignItems = 'center';
return textElement;
}
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';
let theme = this.themeService.getTheme();
if (theme.type === DARK) {
DOM.addClass(profilerTableContainer, VS_DARK_THEME);
} else if (theme.type === HIGH_CONTRAST) {
DOM.addClass(profilerTableContainer, VS_HC_THEME);
}
this.themeService.onThemeChange(e => {
DOM.removeClasses(profilerTableContainer, VS_DARK_THEME, VS_HC_THEME);
if (e.type === DARK) {
DOM.addClass(profilerTableContainer, VS_DARK_THEME);
} else if (e.type === HIGH_CONTRAST) {
DOM.addClass(profilerTableContainer, VS_HC_THEME);
}
});
this._profilerTableEditor = this._instantiationService.createInstance(ProfilerTableEditor);
this._profilerTableEditor.createEditor(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).filter(key => {
return data[key] !== ' ';
}).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);
attachTabbedPanelStyler(this._tabbedPanel, this.themeService);
const expandPanel = () => {
if (this._collapsedPanelAction.collapsed) {
this._collapsedPanelAction.run(this.input);
}
};
this._tabbedPanel.pushTab({
identifier: 'editor',
title: nls.localize('text', "Text"),
view: {
layout: dim => this._editor.layout(dim),
render: parent => parent.appendChild(editorContainer),
focus: () => this._editor.focus()
},
tabSelectedHandler: expandPanel
});
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, {
dataProvider: this._detailTableData, columns: [
{
id: 'label',
name: nls.localize('label', "Label"),
field: 'label',
formatter: textFormatter
},
{
id: 'value',
name: nls.localize('profilerEditor.value', "Value"),
field: 'value',
formatter: textFormatter
}
]
}, {
forceFitColumns: true,
dataItemColumnValueExtractor: slickGridDataItemColumnValueExtractor
});
this._detailTableData.onRowCountChange(() => {
this._detailTable.updateRowCount();
});
const detailTableCopyKeybind = new CopyKeybind();
detailTableCopyKeybind.onCopy((ranges: Slick.Range[]) => {
// we always only get 1 item in the ranges
if (ranges && ranges.length === 1) {
handleCopyRequest(this._clipboardService, this.textResourcePropertiesService, ranges[0], (row, cell) => {
const item = this._detailTableData.getItem(row);
// only 2 columns in this table
return cell === 0 ? item.label : item.value;
});
}
});
this._detailTable.setSelectionModel(new CellSelectionModel());
this._detailTable.registerPlugin(detailTableCopyKeybind);
this._tabbedPanel.pushTab({
identifier: 'detailTable',
title: nls.localize('details', "Details"),
view: {
layout: dim => this._detailTable.layout(dim),
render: parent => parent.appendChild(detailTableContainer),
focus: () => this._detailTable.focus()
},
tabSelectedHandler: expandPanel
});
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(editorContainer);
this._editor.setVisible(true);
this._editorInput = this._instantiationService.createInstance(UntitledEditorInput, URI.from({ scheme: Schemas.untitled }), 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): Promise<void> {
let savedViewState = this._savedTableViewStates.get(input);
this._profilerEditorContextKey.set(true);
if (input instanceof ProfilerInput && input.matches(this.input)) {
if (savedViewState) {
this._profilerTableEditor.restoreViewState(savedViewState);
}
return Promise.resolve(null);
}
return super.setInput(input, options, CancellationToken.None).then(() => {
this._profilerTableEditor.setInput(input);
if (input.viewTemplate) {
this._viewTemplateSelector.selectWithOptionName(input.viewTemplate.name);
} else {
input.viewTemplate = find(this._viewTemplates, i => i.name === 'Standard View');
}
this._actionBar.context = input;
this._tabbedPanel.actionBarContext = input;
if (this._stateListener) {
this._stateListener.dispose();
}
this._stateListener = input.state.onProfilerStateChange(e => this._onStateChange(e));
this._onStateChange({
isConnected: true,
isRunning: true,
isPaused: true,
isStopped: true,
autoscroll: true,
isPanelCollapsed: true
});
this._profilerTableEditor.updateState();
this._profilerTableEditor.focus();
if (savedViewState) {
this._profilerTableEditor.restoreViewState(savedViewState);
}
});
}
public clearInput(): void {
this._profilerEditorContextKey.set(false);
}
public toggleSearch(): void {
if (this._editor.getControl().hasTextFocus()) {
let editor = this._editor.getControl() as ICodeEditor;
let controller = CommonFindController.get(editor);
if (controller) {
controller.start({
forceRevealReplace: false,
seedSearchStringFromGlobalClipboard: false,
seedSearchStringFromSelection: (controller.getState().searchString.length === 0),
shouldFocus: FindStartFocusAction.FocusFindInput,
shouldAnimate: true,
updateSearchScope: false
});
}
} 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;
if (this.input.state.isConnected) {
this._updateToolbar();
// Launch the create session dialog if openning a new window.
let uiState = this._profilerService.getSessionViewState(this.input.id);
let previousSessionName = uiState && uiState.previousSessionName;
if (!this.input.sessionName && !previousSessionName) {
this._profilerService.launchCreateSessionDialog(this.input);
}
this._updateSessionSelector(previousSessionName);
} else {
this._startAction.enabled = false;
this._stopAction.enabled = false;
this._pauseAction.enabled = false;
this._sessionSelector.setOptions([]);
this._sessionSelector.disable();
return;
}
}
if (e.isPaused) {
this._pauseAction.paused = this.input.state.isPaused;
this._updateToolbar();
}
if (e.isStopped || e.isRunning) {
if (this.input.state.isRunning) {
this._updateToolbar();
this._sessionSelector.setOptions([this.input.sessionName]);
this._sessionSelector.selectWithOptionName(this.input.sessionName);
this._sessionSelector.disable();
this._viewTemplateSelector.selectWithOptionName(this.input.viewTemplate.name);
}
if (this.input.state.isStopped) {
this._updateToolbar();
this._updateSessionSelector();
}
}
}
private _updateSessionSelector(previousSessionName: string = undefined) {
this._sessionSelector.enable();
this._profilerService.getXEventSessions(this.input.id).then((r) => {
if (!r) {
r = [];
}
this._sessionSelector.setOptions(r);
this._sessionsList = r;
if (this._sessionsList.length > 0) {
if (!this.input.sessionName) {
this.input.sessionName = previousSessionName;
}
if (this._sessionsList.indexOf(this.input.sessionName) === -1) {
this.input.sessionName = this._sessionsList[0];
}
this._sessionSelector.selectWithOptionName(this.input.sessionName);
}
});
}
private _updateToolbar(): void {
this._startAction.enabled = !this.input.state.isRunning && !this.input.state.isPaused && this.input.state.isConnected;
this._createAction.enabled = !this.input.state.isRunning && !this.input.state.isPaused && this.input.state.isConnected;
this._stopAction.enabled = !this.input.state.isStopped && (this.input.state.isRunning || this.input.state.isPaused) && this.input.state.isConnected;
this._pauseAction.enabled = !this.input.state.isStopped && (this.input.state.isRunning || this.input.state.isPaused && this.input.state.isConnected);
}
public layout(dimension: DOM.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));
}
private saveEditorViewState(): void {
if (this.input && this._profilerTableEditor) {
this._savedTableViewStates.set(this.input, this._profilerTableEditor.saveViewState());
}
}
public focus() {
this._profilerEditorContextKey.set(true);
super.focus();
let savedViewState = this._savedTableViewStates.get(this.input);
if (savedViewState) {
this._profilerTableEditor.restoreViewState(savedViewState);
}
}
}
abstract class SettingsCommand extends Command {
protected getProfilerEditor(accessor: ServicesAccessor): ProfilerEditor {
const activeEditor = accessor.get(IEditorService).activeControl;
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,
weight: KeybindingWeight.EditorContrib
}
});
command.register();

View File

@@ -0,0 +1,348 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/profilerFilterDialog';
import { Button } from 'sql/base/browser/ui/button/button';
import { Modal } from 'sql/workbench/browser/modal/modal';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { attachButtonStyler, attachModalDialogStyler, attachInputBoxStyler } from 'sql/platform/theme/common/styler';
import { KeyCode } from 'vs/base/common/keyCodes';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { localize } from 'vs/nls';
import { ProfilerInput } from 'sql/workbench/contrib/profiler/browser/profilerInput';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { generateUuid } from 'vs/base/common/uuid';
import * as DOM from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ProfilerFilter, ProfilerFilterClause, ProfilerFilterClauseOperator, IProfilerService } from 'sql/workbench/services/profiler/browser/interfaces';
import { ILogService } from 'vs/platform/log/common/log';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
import { find, firstIndex } from 'vs/base/common/arrays';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
const ClearText: string = localize('profilerFilterDialog.clear', "Clear all");
const ApplyText: string = localize('profilerFilterDialog.apply', "Apply");
const OkText: string = localize('profilerFilterDialog.ok', "OK");
const CancelText: string = localize('profilerFilterDialog.cancel', "Cancel");
const DialogTitle: string = localize('profilerFilterDialog.title', "Filters");
const RemoveText: string = localize('profilerFilterDialog.remove', "Remove this clause");
const SaveFilterText: string = localize('profilerFilterDialog.saveFilter', "Save Filter");
const LoadFilterText: string = localize('profilerFilterDialog.loadFilter', "Load Filter");
const AddClauseText: string = localize('profilerFilterDialog.addClauseText', "Add a clause");
const TitleIconClass: string = 'icon filterLabel';
const FieldText: string = localize('profilerFilterDialog.fieldColumn', "Field");
const OperatorText: string = localize('profilerFilterDialog.operatorColumn', "Operator");
const ValueText: string = localize('profilerFilterDialog.valueColumn', "Value");
const Equals: string = '=';
const NotEquals: string = '<>';
const LessThan: string = '<';
const LessThanOrEquals: string = '<=';
const GreaterThan: string = '>';
const GreaterThanOrEquals: string = '>=';
const IsNull: string = localize('profilerFilterDialog.isNullOperator', "Is Null");
const IsNotNull: string = localize('profilerFilterDialog.isNotNullOperator', "Is Not Null");
const Contains: string = localize('profilerFilterDialog.containsOperator', "Contains");
const NotContains: string = localize('profilerFilterDialog.notContainsOperator', "Not Contains");
const StartsWith: string = localize('profilerFilterDialog.startsWithOperator', "Starts With");
const NotStartsWith: string = localize('profilerFilterDialog.notStartsWithOperator', "Not Starts With");
const Operators = [Equals, NotEquals, LessThan, LessThanOrEquals, GreaterThan, GreaterThanOrEquals, GreaterThan, GreaterThanOrEquals, IsNull, IsNotNull, Contains, NotContains, StartsWith, NotStartsWith];
export class ProfilerFilterDialog extends Modal {
private _clauseBuilder: HTMLElement;
private _okButton: Button;
private _cancelButton: Button;
private _applyButton: Button;
private _loadFilterButton: Button;
private _saveFilterButton: Button;
private _input: ProfilerInput;
private _clauseRows: ClauseRowUI[] = [];
constructor(
@IThemeService themeService: IThemeService,
@IClipboardService clipboardService: IClipboardService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@IAdsTelemetryService telemetryService: IAdsTelemetryService,
@IContextKeyService contextKeyService: IContextKeyService,
@ILogService logService: ILogService,
@IContextViewService private contextViewService: IContextViewService,
@IProfilerService private profilerService: IProfilerService,
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService
) {
super('', TelemetryKeys.ProfilerFilter, telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService, { isFlyout: false, hasTitleIcon: true });
}
public open(input: ProfilerInput) {
this._input = input;
this.render();
this.show();
this._okButton.focus();
}
public dispose(): void {
}
public render() {
super.render();
this.title = DialogTitle;
this.titleIconClassName = TitleIconClass;
this._register(attachModalDialogStyler(this, this._themeService));
this._saveFilterButton = this.addFooterButton(SaveFilterText, () => this.saveFilter(), 'left');
this._loadFilterButton = this.addFooterButton(LoadFilterText, () => this.loadSavedFilter(), 'left');
this._applyButton = this.addFooterButton(ApplyText, () => this.filterSession());
this._okButton = this.addFooterButton(OkText, () => this.handleOkButtonClick());
this._cancelButton = this.addFooterButton(CancelText, () => this.hide());
this._register(attachButtonStyler(this._okButton, this._themeService));
this._register(attachButtonStyler(this._cancelButton, this._themeService));
this._register(attachButtonStyler(this._applyButton, this._themeService));
this._register(attachButtonStyler(this._saveFilterButton, this._themeService));
this._register(attachButtonStyler(this._loadFilterButton, this._themeService));
}
protected renderBody(container: HTMLElement) {
const body = DOM.append(container, DOM.$('.profiler-filter-dialog'));
const clauseTableContainer = DOM.append(body, DOM.$('.clause-table-container'));
this._clauseBuilder = DOM.append(clauseTableContainer, DOM.$('table.profiler-filter-clause-table'));
const headerRow = DOM.append(this._clauseBuilder, DOM.$('tr'));
DOM.append(headerRow, DOM.$('td')).innerText = FieldText;
DOM.append(headerRow, DOM.$('td')).innerText = OperatorText;
DOM.append(headerRow, DOM.$('td')).innerText = ValueText;
DOM.append(headerRow, DOM.$('td')).innerText = '';
this._input.filter.clauses.forEach(clause => {
this.addClauseRow(true, clause.field, this.convertToOperatorString(clause.operator), clause.value);
});
this.createClauseTableActionLink(AddClauseText, body, () => { this.addClauseRow(false); });
this.createClauseTableActionLink(ClearText, body, () => { this.handleClearButtonClick(); });
}
protected layout(height?: number): void {
// Nothing to re-layout
}
/* espace key */
protected onClose() {
this.hide();
}
/* enter key */
protected onAccept() {
this.handleOkButtonClick();
}
private handleOkButtonClick(): void {
this.filterSession();
this.hide();
}
private handleClearButtonClick() {
this._clauseRows.forEach(clause => {
clause.row.remove();
});
this._clauseRows = [];
}
private createClauseTableActionLink(text: string, parent: HTMLElement, handler: () => void): void {
const actionLink = DOM.append(parent, DOM.$('.profiler-filter-clause-table-action', {
'tabIndex': '0',
'role': 'button'
}));
actionLink.innerText = text;
DOM.addDisposableListener(actionLink, DOM.EventType.CLICK, handler);
DOM.addStandardDisposableListener(actionLink, DOM.EventType.KEY_DOWN, (e: StandardKeyboardEvent) => {
if (e.equals(KeyCode.Space) || e.equals(KeyCode.Enter)) {
handler();
e.stopPropagation();
}
});
}
private createSelectBox(container: HTMLElement, options: string[], selectedOption: string, ariaLabel: string): SelectBox {
const dropdown = new SelectBox(options, selectedOption, this.contextViewService, undefined, { ariaLabel: ariaLabel });
dropdown.render(container);
this._register(attachSelectBoxStyler(dropdown, this._themeService));
return dropdown;
}
private filterSession(): void {
this._input.filterSession(this.getFilter());
}
private saveFilter(): void {
this.profilerService.saveFilter(this.getFilter());
}
private loadSavedFilter(): void {
// for now we only have one saved filter, this is enough for what user asked for so far.
const savedFilters = this.profilerService.getFilters();
if (savedFilters && savedFilters.length > 0) {
const savedFilter = savedFilters[0];
this._clauseRows.forEach(clause => {
clause.row.remove();
});
this._clauseRows = [];
savedFilter.clauses.forEach(clause => {
this.addClauseRow(true, clause.field, this.convertToOperatorString(clause.operator), clause.value);
});
}
}
private getFilter(): ProfilerFilter {
const clauses: ProfilerFilterClause[] = [];
this._clauseRows.forEach(row => {
clauses.push({
field: row.field.value,
operator: this.convertToOperatorEnum(row.operator.value),
value: row.value.value
});
});
return {
name: 'default',
clauses: clauses
};
}
private addClauseRow(setInitialValue: boolean, field?: string, operator?: string, value?: string): void {
const columns = this._input.columns.map(column => column.name);
if (field && !find(columns, x => x === field)) {
return;
}
const row = DOM.append(this._clauseBuilder, DOM.$('tr'));
const clauseId = generateUuid();
const fieldDropDown = this.createSelectBox(DOM.append(row, DOM.$('td')), columns, columns[0], FieldText);
const operatorDropDown = this.createSelectBox(DOM.append(row, DOM.$('td')), Operators, Operators[0], OperatorText);
const valueText = new InputBox(DOM.append(row, DOM.$('td')), undefined, {});
this._register(attachInputBoxStyler(valueText, this._themeService));
const removeCell = DOM.append(row, DOM.$('td'));
const removeClauseButton = DOM.append(removeCell, DOM.$('.profiler-filter-remove-condition.codicon.remove', {
'tabIndex': '0',
'aria-label': RemoveText,
'title': RemoveText,
'role': 'button'
}));
DOM.addStandardDisposableListener(removeClauseButton, DOM.EventType.KEY_DOWN, (e: StandardKeyboardEvent) => {
if (e.equals(KeyCode.Space) || e.equals(KeyCode.Enter)) {
this.removeRow(clauseId);
e.stopPropagation();
}
});
DOM.addDisposableListener(removeClauseButton, DOM.EventType.CLICK, (e: MouseEvent) => {
this.removeRow(clauseId);
});
if (setInitialValue) {
fieldDropDown.selectWithOptionName(field);
operatorDropDown.selectWithOptionName(operator);
valueText.value = value;
}
this._clauseRows.push({
id: clauseId,
row,
field: fieldDropDown,
operator: operatorDropDown,
value: valueText
});
}
private removeRow(clauseId: string) {
const idx = firstIndex(this._clauseRows, (entry) => { return entry.id === clauseId; });
if (idx !== -1) {
this._clauseRows[idx].row.remove();
this._clauseRows.splice(idx, 1);
}
}
private convertToOperatorEnum(operator: string): ProfilerFilterClauseOperator {
switch (operator) {
case Equals:
return ProfilerFilterClauseOperator.Equals;
case NotEquals:
return ProfilerFilterClauseOperator.NotEquals;
case LessThan:
return ProfilerFilterClauseOperator.LessThan;
case LessThanOrEquals:
return ProfilerFilterClauseOperator.LessThanOrEquals;
case GreaterThan:
return ProfilerFilterClauseOperator.GreaterThan;
case GreaterThanOrEquals:
return ProfilerFilterClauseOperator.GreaterThanOrEquals;
case IsNull:
return ProfilerFilterClauseOperator.IsNull;
case IsNotNull:
return ProfilerFilterClauseOperator.IsNotNull;
case Contains:
return ProfilerFilterClauseOperator.Contains;
case NotContains:
return ProfilerFilterClauseOperator.NotContains;
case StartsWith:
return ProfilerFilterClauseOperator.StartsWith;
case NotStartsWith:
return ProfilerFilterClauseOperator.NotStartsWith;
default:
throw new Error(`Not a valid operator: ${operator}`);
}
}
private convertToOperatorString(operator: ProfilerFilterClauseOperator): string {
switch (operator) {
case ProfilerFilterClauseOperator.Equals:
return Equals;
case ProfilerFilterClauseOperator.NotEquals:
return NotEquals;
case ProfilerFilterClauseOperator.LessThan:
return LessThan;
case ProfilerFilterClauseOperator.LessThanOrEquals:
return LessThanOrEquals;
case ProfilerFilterClauseOperator.GreaterThan:
return GreaterThan;
case ProfilerFilterClauseOperator.GreaterThanOrEquals:
return GreaterThanOrEquals;
case ProfilerFilterClauseOperator.IsNull:
return IsNull;
case ProfilerFilterClauseOperator.IsNotNull:
return IsNotNull;
case ProfilerFilterClauseOperator.Contains:
return Contains;
case ProfilerFilterClauseOperator.NotContains:
return NotContains;
case ProfilerFilterClauseOperator.StartsWith:
return StartsWith;
case ProfilerFilterClauseOperator.NotStartsWith:
return NotStartsWith;
default:
throw new Error(`Not a valid operator: ${operator}`);
}
}
}
interface ClauseRowUI {
id: string;
row: HTMLElement;
field: SelectBox;
operator: SelectBox;
value: InputBox;
}

View File

@@ -0,0 +1,570 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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, CONTEXT_FIND_INPUT_FOCUSED } from 'vs/editor/contrib/find/findModel';
import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/findState';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService';
import * as colors 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', "Your search returned a large number of results, only the first 999 matches will be highlighted.");
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 PROFILER_MAX_MATCHES = 999;
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;
private searchTimeoutHandle: any;
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.onFindReplaceStateChange((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.onDidFocus(() => {
this._findInputFocussed.set(true);
});
this._focusTracker.onDidBlur(() => {
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 >= PROFILER_MAX_MATCHES) {
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 >= PROFILER_MAX_MATCHES) {
matchesCount = PROFILER_MAX_MATCHES + '+';
}
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(colors.inputActiveOptionBorder),
inputBackground: theme.getColor(colors.inputBackground),
inputForeground: theme.getColor(colors.inputForeground),
inputBorder: theme.getColor(colors.inputBorder),
inputValidationInfoBackground: theme.getColor(colors.inputValidationInfoBackground),
inputValidationInfoBorder: theme.getColor(colors.inputValidationInfoBorder),
inputValidationWarningBackground: theme.getColor(colors.inputValidationWarningBackground),
inputValidationWarningBorder: theme.getColor(colors.inputValidationWarningBorder),
inputValidationErrorBackground: theme.getColor(colors.inputValidationErrorBackground),
inputValidationErrorBorder: theme.getColor(colors.inputValidationErrorBorder)
};
this._findInput.style(inputStyles);
}
// ----- Public
public focusFindInput(): void {
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().then(null, onUnexpectedError);
e.preventDefault();
return;
}
if (e.equals(KeyMod.Shift | KeyCode.Enter)) {
this._tableController.getAction(ACTION_IDS.FIND_NEXT).run().then(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, true, {
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(() => {
let self = this;
if (self.searchTimeoutHandle) {
clearTimeout(self.searchTimeoutHandle);
}
this.searchTimeoutHandle = setTimeout(function () {
self._state.change({ searchString: self._findInput.getValue() }, true);
}, 300);
}));
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().then(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().then(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.onDidStart((e: ISashEvent) => {
originalWidth = dom.getTotalWidth(this._domNode);
}));
this._register(this._resizeSash.onDidChange((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 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);
}
}

View File

@@ -0,0 +1,316 @@
/*---------------------------------------------------------------------------------------------
* 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, IProfilerViewTemplate, ProfilerFilter } from 'sql/workbench/services/profiler/browser/interfaces';
import { ProfilerState } from 'sql/workbench/contrib/profiler/common/profilerState';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import * as azdata from 'azdata';
import * as nls from 'vs/nls';
import { EditorInput, ConfirmResult } from 'vs/workbench/common/editor';
import { IEditorModel } from 'vs/platform/editor/common/editor';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { Event, Emitter } from 'vs/base/common/event';
import { generateUuid } from 'vs/base/common/uuid';
import { IDialogService, IShowResult } from 'vs/platform/dialogs/common/dialogs';
import * as types from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import Severity from 'vs/base/common/severity';
import { FilterData } from 'sql/workbench/services/profiler/browser/profilerFilter';
import { uriPrefixes } from 'sql/platform/connection/common/utils';
import { find } from 'vs/base/common/arrays';
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 _sessionName: string;
private _viewTemplate: IProfilerViewTemplate;
// mapping of event categories to what column they display under
// used for coallescing multiple events with different names to the same column
private _columnMapping: { [event: string]: string } = {};
private _onColumnsChanged = new Emitter<Slick.Column<Slick.SlickData>[]>();
public onColumnsChanged: Event<Slick.Column<Slick.SlickData>[]> = this._onColumnsChanged.event;
private _filter: ProfilerFilter = { clauses: [] };
constructor(
public connection: IConnectionProfile,
@IProfilerService private _profilerService: IProfilerService,
@INotificationService private _notificationService: INotificationService,
@IDialogService private _dialogService: IDialogService
) {
super();
this._state = new ProfilerState();
// set inital state
this.state.change({
isConnected: false,
isStopped: true,
isPaused: false,
isRunning: false,
autoscroll: true
});
this._profilerService.registerSession(uriPrefixes.connection + generateUuid(), connection, this).then((id) => {
this._id = id;
this.state.change({ isConnected: true });
});
let searchFn = (val: { [x: string]: string }, exp: string): Array<number> => {
let ret = new Array<number>();
for (let i = 0; i < this._columns.length; i++) {
let colVal = val[this._columns[i]];
if (colVal && colVal.toLocaleLowerCase().indexOf(exp.toLocaleLowerCase()) > -1) {
ret.push(i);
}
}
return ret;
};
let filterFn = (data: Array<Slick.SlickData>): Array<Slick.SlickData> => {
return FilterData(this._filter, data);
};
this._data = new TableDataView<Slick.SlickData>(undefined, searchFn, undefined, filterFn);
}
public get providerType(): string {
return this.connection ? this.connection.providerName : undefined;
}
public set viewTemplate(template: IProfilerViewTemplate) {
this._data.clear();
this._viewTemplate = template;
let newColumns = this._viewTemplate.columns.reduce<Array<string>>((p, e) => {
p.push(e.name);
return p;
}, []);
let newMapping: { [event: string]: string } = {};
this._viewTemplate.columns.forEach(c => {
c.eventsMapped.forEach(e => {
newMapping[e] = c.name;
});
});
this.setColumnMapping(newColumns, newMapping);
}
public get viewTemplate(): IProfilerViewTemplate {
return this._viewTemplate;
}
public set sessionName(name: string) {
if (!this.state.isRunning || !this.state.isPaused) {
this._sessionName = name;
}
}
public get sessionName(): string {
return this._sessionName;
}
public getTypeId(): string {
return ProfilerInput.ID;
}
public resolve(refresh?: boolean): Promise<IEditorModel> {
return undefined;
}
public getName(): string {
let name: string = nls.localize('profilerInput.profiler', "Profiler");
if (!this.connection) {
return name;
}
name += ': ' + this.connection.serverName.substring(0, 20);
return name;
}
public getResource(): URI {
return URI.from({
scheme: ProfilerInput.SCHEMA,
path: '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 setColumnMapping(columns: Array<string>, mapping: { [event: string]: string }) {
this._columns = columns;
this._columnMapping = mapping;
this._onColumnsChanged.fire(this.columns);
}
public get connectionName(): string {
if (!types.isUndefinedOrNull(this.connection)) {
if (this.connection.databaseName) {
return `${this.connection.serverName} ${this.connection.databaseName}`;
} else {
return `${this.connection.serverName}`;
}
}
else {
return nls.localize('profilerInput.notConnected', "Not connected");
}
}
public get id(): ProfilerSessionID {
return this._id;
}
public get state(): ProfilerState {
return this._state;
}
public get filter(): ProfilerFilter {
return this._filter;
}
public onSessionStopped(notification: azdata.ProfilerSessionStoppedParams) {
this._notificationService.error(nls.localize("profiler.sessionStopped", "XEvent Profiler Session stopped unexpectedly on the server {0}.", this.connection.serverName));
this.state.change({
isStopped: true,
isPaused: false,
isRunning: false
});
}
public onProfilerSessionCreated(params: azdata.ProfilerSessionCreatedParams) {
if (types.isUndefinedOrNull(params.sessionName) || types.isUndefinedOrNull(params.templateName)) {
this._notificationService.error(nls.localize("profiler.sessionCreationError", "Error while starting new session"));
} else {
this._sessionName = params.sessionName;
let sessionTemplate = find(this._profilerService.getSessionTemplates(), (template) => {
return template.name === params.templateName;
});
if (!types.isUndefinedOrNull(sessionTemplate)) {
let newView = find(this._profilerService.getViewTemplates(), (view) => {
return view.name === sessionTemplate.defaultView;
});
if (!types.isUndefinedOrNull(newView)) {
this.viewTemplate = newView;
}
}
this.data.clear();
this.state.change({
isStopped: false,
isPaused: false,
isRunning: true
});
}
}
public onSessionStateChanged(state: ProfilerState) {
this.state.change(state);
}
public onMoreRows(eventMessage: azdata.ProfilerSessionEvents) {
if (eventMessage.eventsLost) {
this._notificationService.warn(nls.localize("profiler.eventsLost", "The XEvent Profiler session for {0} has lost events.", this.connection.serverName));
}
let newEvents = [];
for (let i: number = 0; i < eventMessage.events.length && i < 500; ++i) {
let e: azdata.ProfilerEvent = eventMessage.events[i];
let data = {};
data['EventClass'] = e.name;
data['StartTime'] = e.timestamp;
// Using ' ' instead of '' fixed the error where clicking through events
// with empty text fields causes future text panes to be highlighted.
// This is a temporary fix
data['TextData'] = ' ';
for (let key in e.values) {
let columnName = this._columnMapping[key];
if (columnName) {
let value = e.values[key];
data[columnName] = value;
}
}
newEvents.push(data);
}
if (newEvents.length > 0) {
this._data.push(newEvents);
}
}
filterSession(filter: ProfilerFilter) {
this._filter = filter;
if (this._filter.clauses.length !== 0) {
this.data.filter();
} else {
this.data.clearFilter();
}
}
clearFilter() {
this._filter = { clauses: [] };
this.data.clearFilter();
}
confirmSave(): Promise<ConfirmResult> {
if (this.state.isRunning || this.state.isPaused) {
return this._dialogService.show(Severity.Warning,
nls.localize('confirmStopProfilerSession', "Would you like to stop the running XEvent session?"),
[
nls.localize('profilerClosingActions.yes', "Yes"),
nls.localize('profilerClosingActions.no', "No"),
nls.localize('profilerClosingActions.cancel', "Cancel")
]).then((selection: IShowResult) => {
if (selection.choice === 0) {
this._profilerService.stopSession(this.id);
return ConfirmResult.DONT_SAVE;
} else if (selection.choice === 1) {
return ConfirmResult.DONT_SAVE;
} else {
return ConfirmResult.CANCEL;
}
});
} else {
return Promise.resolve(ConfirmResult.DONT_SAVE);
}
}
isDirty(): boolean {
return this.state.isRunning || this.state.isPaused;
}
dispose() {
super.dispose();
this._profilerService.disconnectSession(this.id);
}
}

View 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 * as DOM from 'vs/base/browser/dom';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
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 { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { EditorOptions } from 'vs/workbench/common/editor';
import { StandaloneCodeEditor } from 'vs/editor/standalone/browser/standaloneCodeEditor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IHostService } from 'vs/workbench/services/host/browser/host';
class ProfilerResourceCodeEditor extends StandaloneCodeEditor {
// 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,
@ITextFileService textFileService: ITextFileService,
@IEditorService protected editorService: IEditorService,
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IHostService hostService: IHostService
) {
super(ProfilerResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService);
}
public createEditorControl(parent: HTMLElement, configuration: IEditorOptions): editorCommon.IEditor {
return this.instantiationService.createInstance(ProfilerResourceCodeEditor, parent, 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): Promise<void> {
return super.setInput(input, options, CancellationToken.None)
.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: DOM.Dimension) {
this.getControl().layout(dimension);
}
}

View File

@@ -0,0 +1,318 @@
/*---------------------------------------------------------------------------------------------
* 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 'sql/workbench/contrib/profiler/common/interfaces';
import { ProfilerInput } from 'sql/workbench/contrib/profiler/browser/profilerInput';
import { Table } from 'sql/base/browser/ui/table/table';
import { attachTableStyler } from 'sql/platform/theme/common/styler';
import { RowSelectionModel } from 'sql/base/browser/ui/table/plugins/rowSelectionModel.plugin';
import { IProfilerStateChangedEvent } from 'sql/workbench/contrib/profiler/common/profilerState';
import { FindWidget, ITableController, IConfigurationChangedEvent, ACTION_IDS, PROFILER_MAX_MATCHES } from 'sql/workbench/contrib/profiler/browser/profilerFindWidget';
import { ProfilerFindNext, ProfilerFindPrevious } from 'sql/workbench/contrib/profiler/browser/profilerActions';
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/findState';
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';
import { Dimension } from 'vs/base/browser/dom';
import { textFormatter, slickGridDataItemColumnValueExtractor } from 'sql/base/browser/ui/table/formatters';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar';
import { localize } from 'vs/nls';
import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugin';
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
import { handleCopyRequest } from 'sql/workbench/contrib/profiler/browser/profilerCopyHandler';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
export interface ProfilerTableViewState {
scrollTop: number;
scrollLeft: number;
}
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 _statusbarItem: IDisposable;
private _showStatusBarItem: boolean;
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,
@IStorageService storageService: IStorageService,
@IStatusbarService private _statusbarService: IStatusbarService,
@IClipboardService private _clipboardService: IClipboardService,
@ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService
) {
super(ProfilerTableEditor.ID, telemetryService, _themeService, storageService);
this._actionMap[ACTION_IDS.FIND_NEXT] = this._instantiationService.createInstance(ProfilerFindNext, this);
this._actionMap[ACTION_IDS.FIND_PREVIOUS] = this._instantiationService.createInstance(ProfilerFindPrevious, this);
this._showStatusBarItem = true;
}
public createEditor(parent: HTMLElement): void {
this._overlay = document.createElement('div');
this._overlay.className = 'overlayWidgets';
this._overlay.style.width = '100%';
this._overlay.style.zIndex = '4';
parent.appendChild(this._overlay);
this._profilerTable = new Table(parent, {
sorter: (args) => {
let input = this.input as ProfilerInput;
if (input && input.data) {
input.data.sort(args);
}
}
}, {
dataItemColumnValueExtractor: slickGridDataItemColumnValueExtractor
});
this._profilerTable.setSelectionModel(new RowSelectionModel());
const copyKeybind = new CopyKeybind();
copyKeybind.onCopy((e) => {
// in context of this table, the selection mode is row selection, copy the whole row will get a lot of unwanted data
// ignore the passed in range and create a range so that it only copies the currently selected cell value.
const activeCell = this._profilerTable.activeCell;
handleCopyRequest(this._clipboardService, this.textResourcePropertiesService, new Slick.Range(activeCell.row, activeCell.cell), (row, cell) => {
const fieldName = this._input.columns[cell].field;
return this._input.data.getItem(row)[fieldName];
});
});
this._profilerTable.registerPlugin(copyKeybind);
attachTableStyler(this._profilerTable, this._themeService);
this._findState = new FindReplaceState();
this._findState.onFindReplaceStateChange(e => this._onFindStateChange(e));
this._finder = new FindWidget(
this,
this._findState,
this._contextViewService,
this._keybindingService,
this._contextKeyService,
this._themeService
);
}
public setInput(input: ProfilerInput): Promise<void> {
this._showStatusBarItem = true;
this._input = input;
this._updateRowCountStatus();
if (this._columnListener) {
this._columnListener.dispose();
}
this._columnListener = input.onColumnsChanged(e => {
this._profilerTable.columns = e.map(e => {
e.formatter = textFormatter;
return e;
});
this._profilerTable.autosizeColumns();
});
if (this._stateListener) {
this._stateListener.dispose();
}
this._stateListener = input.state.onProfilerStateChange(e => this._onStateChange(e));
input.data.onRowCountChange(() => {
this._profilerTable.updateRowCount();
this._updateRowCountStatus();
});
input.data.onFilterStateChange(() => {
this._profilerTable.grid.invalidateAllRows();
this._profilerTable.updateRowCount();
this._updateRowCountStatus();
});
if (this._findCountChangeListener) {
this._findCountChangeListener.dispose();
}
this._findCountChangeListener = input.data.onFindCountChange(() => this._updateFinderMatchState());
this._profilerTable.setData(input.data);
this._profilerTable.columns = input.columns.map(c => {
c.formatter = textFormatter;
return c;
});
this._profilerTable.autosizeColumns();
this._input.data.currentFindPosition.then(val => {
this._profilerTable.setActiveCell(val.row, val.col);
this._updateFinderMatchState();
}, er => { });
this._input.onDispose(() => {
this._disposeStatusbarItem();
});
return Promise.resolve(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._profilerTable.autosizeColumns();
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, PROFILER_MAX_MATCHES).then(p => {
if (p) {
this._profilerTable.setActiveCell(p.row, p.col);
this._updateFinderMatchState();
this._finder.focusFindInput();
}
});
} 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);
}
}
private _updateRowCountStatus(): void {
if (this._showStatusBarItem) {
let message = this._input.data.filterEnabled ?
localize('ProfilerTableEditor.eventCountFiltered', "Events (Filtered): {0}/{1}", this._input.data.getLength(), this._input.data.getLengthNonFiltered())
: localize('ProfilerTableEditor.eventCount', "Events: {0}", this._input.data.getLength());
this._disposeStatusbarItem();
this._statusbarItem = this._statusbarService.addEntry({ text: message }, 'status.eventCount', localize('status.eventCount', "Event Count"), StatusbarAlignment.RIGHT);
}
}
private _disposeStatusbarItem() {
if (this._statusbarItem) {
this._statusbarItem.dispose();
}
}
public saveViewState(): ProfilerTableViewState {
this._disposeStatusbarItem();
this._showStatusBarItem = false;
let viewElement = this._profilerTable.grid.getCanvasNode().parentElement;
return {
scrollTop: viewElement.scrollTop,
scrollLeft: viewElement.scrollLeft
};
}
public restoreViewState(state: ProfilerTableViewState): void {
this._showStatusBarItem = true;
this._updateRowCountStatus();
let viewElement = this._profilerTable.grid.getCanvasNode().parentElement;
viewElement.scrollTop = state.scrollTop;
viewElement.scrollLeft = state.scrollLeft;
}
}