Skip to content

Commit

Permalink
[debug] added function breakpoints support
Browse files Browse the repository at this point in the history
- fix #5767 - function breakpoints support
- fix #5788 - preserve breakpoints identity in the plugin host process

Signed-off-by: Anton Kosyakov <[email protected]>
  • Loading branch information
akosyakov committed Aug 24, 2019
1 parent 20ed106 commit 4a6712e
Show file tree
Hide file tree
Showing 20 changed files with 863 additions and 336 deletions.
98 changes: 84 additions & 14 deletions packages/debug/src/browser/breakpoint/breakpoint-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,29 @@
********************************************************************************/

import { injectable, inject } from 'inversify';
import { Emitter, Event } from '@theia/core/lib/common';
import { Emitter } from '@theia/core/lib/common';
import { StorageService } from '@theia/core/lib/browser';
import { Marker } from '@theia/markers/lib/common/marker';
import { MarkerManager } from '@theia/markers/lib/browser/marker-manager';
import URI from '@theia/core/lib/common/uri';
import { SourceBreakpoint, BREAKPOINT_KIND, ExceptionBreakpoint } from './breakpoint-marker';
import { SourceBreakpoint, BREAKPOINT_KIND, ExceptionBreakpoint, FunctionBreakpoint, BaseBreakpoint } from './breakpoint-marker';

export interface BreakpointsChangeEvent {
export interface BreakpointsChangeEvent<T extends BaseBreakpoint> {
uri: URI
added: SourceBreakpoint[]
removed: SourceBreakpoint[]
changed: SourceBreakpoint[]
added: T[]
removed: T[]
changed: T[]
}
export type SourceBreakpointsChangeEvent = BreakpointsChangeEvent<SourceBreakpoint>;
export type FunctionBreakpointsChangeEvent = BreakpointsChangeEvent<FunctionBreakpoint>;

@injectable()
export class BreakpointManager extends MarkerManager<SourceBreakpoint> {

static EXCEPTION_URI = new URI('debug:exception://');

static FUNCTION_URI = new URI('debug:function://');

protected readonly owner = 'breakpoint';

@inject(StorageService)
Expand All @@ -43,8 +47,11 @@ export class BreakpointManager extends MarkerManager<SourceBreakpoint> {
return BREAKPOINT_KIND;
}

protected readonly onDidChangeBreakpointsEmitter = new Emitter<BreakpointsChangeEvent>();
readonly onDidChangeBreakpoints: Event<BreakpointsChangeEvent> = this.onDidChangeBreakpointsEmitter.event;
protected readonly onDidChangeBreakpointsEmitter = new Emitter<SourceBreakpointsChangeEvent>();
readonly onDidChangeBreakpoints = this.onDidChangeBreakpointsEmitter.event;

protected readonly onDidChangeFunctionBreakpointsEmitter = new Emitter<FunctionBreakpointsChangeEvent>();
readonly onDidChangeFunctionBreakpoints = this.onDidChangeFunctionBreakpointsEmitter.event;

setMarkers(uri: URI, owner: string, newMarkers: SourceBreakpoint[]): Marker<SourceBreakpoint>[] {
const result = super.setMarkers(uri, owner, newMarkers);
Expand Down Expand Up @@ -113,6 +120,17 @@ export class BreakpointManager extends MarkerManager<SourceBreakpoint> {
this.fireOnDidChangeMarkers(uri);
}
}
let didChangeFunction = false;
for (const breakpoint of this.getFunctionBreakpoints()) {
if (breakpoint.enabled !== enabled) {
breakpoint.enabled = enabled;
didChangeFunction = true;

}
}
if (didChangeFunction) {
this.fireOnDidChangeMarkers(BreakpointManager.FUNCTION_URI);
}
}

protected _breakpointsEnabled = true;
Expand All @@ -125,6 +143,7 @@ export class BreakpointManager extends MarkerManager<SourceBreakpoint> {
for (const uri of this.getUris()) {
this.fireOnDidChangeMarkers(new URI(uri));
}
this.fireOnDidChangeMarkers(BreakpointManager.FUNCTION_URI);
}
}

Expand Down Expand Up @@ -161,30 +180,80 @@ export class BreakpointManager extends MarkerManager<SourceBreakpoint> {
}
}

protected functionBreakpoints: FunctionBreakpoint[] = [];

getFunctionBreakpoints(): FunctionBreakpoint[] {
return this.functionBreakpoints;
}

setFunctionBreakpoints(functionBreakpoints: FunctionBreakpoint[]): void {
const oldBreakpoints = new Map(this.functionBreakpoints.map(b => [b.id, b] as [string, FunctionBreakpoint]));

this.functionBreakpoints = functionBreakpoints;
this.fireOnDidChangeMarkers(BreakpointManager.FUNCTION_URI);

const added: FunctionBreakpoint[] = [];
const removed: FunctionBreakpoint[] = [];
const changed: FunctionBreakpoint[] = [];
const ids = new Set<string>();
for (const newBreakpoint of functionBreakpoints) {
ids.add(newBreakpoint.id);
if (oldBreakpoints.has(newBreakpoint.id)) {
changed.push(newBreakpoint);
} else {
added.push(newBreakpoint);
}
}
for (const [id, breakpoint] of oldBreakpoints.entries()) {
if (!ids.has(id)) {
removed.push(breakpoint);
}
}
this.onDidChangeFunctionBreakpointsEmitter.fire({ uri: BreakpointManager.FUNCTION_URI, added, removed, changed });
}

hasBreakpoints(): boolean {
return !!this.getUris().next().value || !!this.functionBreakpoints.length;
}

removeBreakpoints(): void {
this.cleanAllMarkers();
this.setFunctionBreakpoints([]);
}

async load(): Promise<void> {
const data = await this.storage.getData<BreakpointManager.Data>('breakpoints', {
breakpointsEnabled: true,
breakpoints: {},
exceptionBreakpoints: []
breakpoints: {}
});
this._breakpointsEnabled = data.breakpointsEnabled;
// tslint:disable-next-line:forin
for (const uri in data.breakpoints) {
this.setBreakpoints(new URI(uri), data.breakpoints[uri]);
}
this.setExceptionBreakpoints(data.exceptionBreakpoints);
if (data.functionBreakpoints) {
this.setFunctionBreakpoints(data.functionBreakpoints);
}
if (data.exceptionBreakpoints) {
this.setExceptionBreakpoints(data.exceptionBreakpoints);
}
}

save(): void {
const data: BreakpointManager.Data = {
breakpointsEnabled: this._breakpointsEnabled,
breakpoints: {},
exceptionBreakpoints: [...this.exceptionBreakpoints.values()]
breakpoints: {}
};
const uris = this.getUris();
for (const uri of uris) {
data.breakpoints[uri] = this.findMarkers({ uri: new URI(uri) }).map(marker => marker.data);
}
if (this.functionBreakpoints.length) {
data.functionBreakpoints = this.functionBreakpoints;
}
if (this.exceptionBreakpoints.size) {
data.exceptionBreakpoints = [...this.exceptionBreakpoints.values()];
}
this.storage.setData('breakpoints', data);
}

Expand All @@ -195,6 +264,7 @@ export namespace BreakpointManager {
breakpoints: {
[uri: string]: SourceBreakpoint[]
}
exceptionBreakpoints: ExceptionBreakpoint[]
exceptionBreakpoints?: ExceptionBreakpoint[]
functionBreakpoints?: FunctionBreakpoint[]
}
}
23 changes: 21 additions & 2 deletions packages/debug/src/browser/breakpoint/breakpoint-marker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ import { DebugProtocol } from 'vscode-debugprotocol/lib/debugProtocol';

export const BREAKPOINT_KIND = 'breakpoint';

export interface SourceBreakpoint {
export interface BaseBreakpoint {
id: string;
uri: string;
enabled: boolean;
}

export interface SourceBreakpoint extends BaseBreakpoint {
uri: string;
raw: DebugProtocol.SourceBreakpoint;
}
export namespace SourceBreakpoint {
Expand Down Expand Up @@ -65,3 +68,19 @@ export namespace ExceptionBreakpoint {
};
}
}

export interface FunctionBreakpoint extends BaseBreakpoint {
raw: DebugProtocol.FunctionBreakpoint;
}
export namespace FunctionBreakpoint {
export function create(data: DebugProtocol.FunctionBreakpoint, origin?: FunctionBreakpoint): FunctionBreakpoint {
return {
id: origin ? origin.id : UUID.uuid4(),
enabled: origin ? origin.enabled : true,
raw: {
...(origin && origin.raw),
...data
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,20 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { AbstractViewContribution, ApplicationShell, KeybindingRegistry, Widget, CompositeTreeNode } from '@theia/core/lib/browser';
import { AbstractViewContribution, ApplicationShell, KeybindingRegistry, Widget, CompositeTreeNode, LabelProvider } from '@theia/core/lib/browser';
import { injectable, inject } from 'inversify';
import { ThemeService } from '@theia/core/lib/browser/theming';
import { MenuModelRegistry, CommandRegistry, MAIN_MENU_BAR, Command, Emitter, Mutable } from '@theia/core/lib/common';
import { DebugViewLocation } from '../common/debug-configuration';
import { EditorKeybindingContexts } from '@theia/editor/lib/browser';
import { EditorKeybindingContexts, EditorManager } from '@theia/editor/lib/browser';
import { DebugSessionManager } from './debug-session-manager';
import { DebugWidget } from './view/debug-widget';
import { FunctionBreakpoint } from './breakpoint/breakpoint-marker';
import { BreakpointManager } from './breakpoint/breakpoint-manager';
import { DebugConfigurationManager } from './debug-configuration-manager';
import { DebugState, DebugSession } from './debug-session';
import { DebugBreakpointsWidget } from './view/debug-breakpoints-widget';
import { DebugBreakpoint } from './model/debug-breakpoint';
import { DebugSourceBreakpoint } from './model/debug-source-breakpoint';
import { DebugThreadsWidget } from './view/debug-threads-widget';
import { DebugThread } from './model/debug-thread';
import { DebugStackFramesWidget } from './view/debug-stack-frames-widget';
Expand All @@ -45,6 +46,7 @@ import { TabBarToolbarContribution, TabBarToolbarRegistry, TabBarToolbarItem } f
import { DebugWatchWidget } from './view/debug-watch-widget';
import { DebugWatchExpression } from './view/debug-watch-expression';
import { DebugWatchManager } from './debug-watch-manager';
import { DebugFunctionBreakpoint } from './model/debug-function-breakpoint';

export namespace DebugMenus {
export const DEBUG = [...MAIN_MENU_BAR, '6_debug'];
Expand Down Expand Up @@ -148,6 +150,11 @@ export namespace DebugCommands {
category: DEBUG_CATEGORY,
label: 'Add Logpoint...',
};
export const ADD_FUNCTION_BREAKPOINT: Command = {
id: 'debug.breakpoint.add.function',
category: DEBUG_CATEGORY,
label: 'Add Function Breakpoint...',
};
export const ENABLE_ALL_BREAKPOINTS: Command = {
id: 'debug.breakpoint.enableAll',
category: DEBUG_CATEGORY,
Expand Down Expand Up @@ -396,6 +403,12 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi
@inject(DebugWatchManager)
protected readonly watchManager: DebugWatchManager;

@inject(LabelProvider)
protected readonly labelProvider: LabelProvider;

@inject(EditorManager)
protected readonly editorManager: EditorManager;

constructor() {
super({
widgetId: DebugWidget.ID,
Expand Down Expand Up @@ -713,13 +726,22 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi
execute: () => this.editors.addBreakpoint('logMessage'),
isEnabled: () => !!this.editors.model && !this.editors.anyBreakpoint
});
registry.registerCommand(DebugCommands.ADD_FUNCTION_BREAKPOINT, {
execute: async () => {
const { labelProvider, breakpointManager, editorManager } = this;
const options = { labelProvider, breakpoints: breakpointManager, editorManager };
await new DebugFunctionBreakpoint(FunctionBreakpoint.create({ name: '' }), options).open();
},
isEnabled: widget => !(widget instanceof Widget) || widget instanceof DebugBreakpointsWidget,
isVisible: widget => !(widget instanceof Widget) || widget instanceof DebugBreakpointsWidget
});
registry.registerCommand(DebugCommands.ENABLE_ALL_BREAKPOINTS, {
execute: () => this.breakpointManager.enableAllBreakpoints(true),
isEnabled: () => !!this.breakpointManager.getUris().next().value
isEnabled: () => this.breakpointManager.hasBreakpoints()
});
registry.registerCommand(DebugCommands.DISABLE_ALL_BREAKPOINTS, {
execute: () => this.breakpointManager.enableAllBreakpoints(false),
isEnabled: () => !!this.breakpointManager.getUris().next().value
isEnabled: () => this.breakpointManager.hasBreakpoints()
});
registry.registerCommand(DebugCommands.EDIT_BREAKPOINT, {
execute: async () => {
Expand Down Expand Up @@ -762,8 +784,8 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi
isVisible: () => !!this.selectedLogpoint
});
registry.registerCommand(DebugCommands.REMOVE_ALL_BREAKPOINTS, {
execute: () => this.breakpointManager.cleanAllMarkers(),
isEnabled: () => !!this.breakpointManager.getUris().next().value,
execute: () => this.breakpointManager.removeBreakpoints(),
isEnabled: () => this.breakpointManager.hasBreakpoints(),
isVisible: widget => !(widget instanceof Widget) || (widget instanceof DebugBreakpointsWidget)
});
registry.registerCommand(DebugCommands.TOGGLE_BREAKPOINTS_ENABLED, {
Expand Down Expand Up @@ -1015,7 +1037,8 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi
id: DebugCommands.TOGGLE_BREAKPOINTS_ENABLED.id,
command: DebugCommands.TOGGLE_BREAKPOINTS_ENABLED.id,
icon: 'fa breakpoints-activate',
onDidChange: onDidChangeToggleBreakpointsEnabled.event
onDidChange: onDidChangeToggleBreakpointsEnabled.event,
priority: 1
};
const updateToggleBreakpointsEnabled = () => {
const tooltip = this.breakpointManager.breakpointsEnabled ? 'Deactivate Breakpoints' : 'Activate Breakpoints';
Expand All @@ -1024,14 +1047,20 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi
onDidChangeToggleBreakpointsEnabled.fire(undefined);
}
};
toolbar.registerItem({
id: DebugCommands.ADD_FUNCTION_BREAKPOINT.id,
command: DebugCommands.ADD_FUNCTION_BREAKPOINT.id,
icon: 'theia-add-icon',
tooltip: 'Add Function Breakpoint'
});
updateToggleBreakpointsEnabled();
this.breakpointManager.onDidChangeBreakpoints(updateToggleBreakpointsEnabled);
toolbar.registerItem(toggleBreakpointsEnabled);
toolbar.registerItem({
id: DebugCommands.REMOVE_ALL_BREAKPOINTS.id,
command: DebugCommands.REMOVE_ALL_BREAKPOINTS.id,
icon: 'theia-remove-all-icon',
priority: 1
priority: 2
});

toolbar.registerItem({
Expand Down Expand Up @@ -1141,15 +1170,15 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi
const { currentWidget } = this.shell;
return currentWidget instanceof DebugBreakpointsWidget && currentWidget || undefined;
}
get selectedAnyBreakpoint(): DebugBreakpoint | undefined {
get selectedAnyBreakpoint(): DebugSourceBreakpoint | undefined {
const { breakpoints } = this;
return breakpoints && breakpoints.selectedElement instanceof DebugBreakpoint && breakpoints.selectedElement || undefined;
return breakpoints && breakpoints.selectedElement instanceof DebugSourceBreakpoint && breakpoints.selectedElement || undefined;
}
get selectedBreakpoint(): DebugBreakpoint | undefined {
get selectedBreakpoint(): DebugSourceBreakpoint | undefined {
const breakpoint = this.selectedAnyBreakpoint;
return breakpoint && !breakpoint.logMessage ? breakpoint : undefined;
}
get selectedLogpoint(): DebugBreakpoint | undefined {
get selectedLogpoint(): DebugSourceBreakpoint | undefined {
const breakpoint = this.selectedAnyBreakpoint;
return breakpoint && !!breakpoint.logMessage ? breakpoint : undefined;
}
Expand Down
Loading

0 comments on commit 4a6712e

Please sign in to comment.