Skip to content

Commit

Permalink
debug: introduce data breakpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
isidorn committed Aug 16, 2019
1 parent d71800c commit a7cc79a
Show file tree
Hide file tree
Showing 15 changed files with 356 additions and 43 deletions.
29 changes: 23 additions & 6 deletions src/vs/workbench/api/browser/mainThreadDebugService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@

import { DisposableStore } from 'vs/base/common/lifecycle';
import { URI as uri } from 'vs/base/common/uri';
import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory, IDataBreakpoint } from 'vs/workbench/contrib/debug/common/debug';
import {
ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext,
IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto
IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto, IDataBreakpointDto
} from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import severity from 'vs/base/common/severity';
Expand Down Expand Up @@ -110,15 +110,16 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
// send all breakpoints
const bps = this.debugService.getModel().getBreakpoints();
const fbps = this.debugService.getModel().getFunctionBreakpoints();
const dbps = this.debugService.getModel().getDataBreakpoints();
if (bps.length > 0 || fbps.length > 0) {
this._proxy.$acceptBreakpointsDelta({
added: this.convertToDto(bps).concat(this.convertToDto(fbps))
added: this.convertToDto(bps).concat(this.convertToDto(fbps)).concat(this.convertToDto(dbps))
});
}
}
}

public $registerBreakpoints(DTOs: Array<ISourceMultiBreakpointDto | IFunctionBreakpointDto>): Promise<void> {
public $registerBreakpoints(DTOs: Array<ISourceMultiBreakpointDto | IFunctionBreakpointDto | IDataBreakpointDto>): Promise<void> {

for (let dto of DTOs) {
if (dto.type === 'sourceMulti') {
Expand All @@ -136,14 +137,17 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
this.debugService.addBreakpoints(uri.revive(dto.uri), rawbps, 'extension');
} else if (dto.type === 'function') {
this.debugService.addFunctionBreakpoint(dto.functionName, dto.id);
} else if (dto.type === 'data') {
this.debugService.addDataBreakpoint(dto.label, dto.dataId, dto.canPersist);
}
}
return Promise.resolve();
}

public $unregisterBreakpoints(breakpointIds: string[], functionBreakpointIds: string[]): Promise<void> {
public $unregisterBreakpoints(breakpointIds: string[], functionBreakpointIds: string[], dataBreakpointIds: string[]): Promise<void> {
breakpointIds.forEach(id => this.debugService.removeBreakpoints(id));
functionBreakpointIds.forEach(id => this.debugService.removeFunctionBreakpoints(id));
dataBreakpointIds.forEach(id => this.debugService.removeDataBreakpoints(id));
return Promise.resolve();
}

Expand Down Expand Up @@ -294,7 +298,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
return undefined;
}

private convertToDto(bps: (ReadonlyArray<IBreakpoint | IFunctionBreakpoint>)): Array<ISourceBreakpointDto | IFunctionBreakpointDto> {
private convertToDto(bps: (ReadonlyArray<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint>)): Array<ISourceBreakpointDto | IFunctionBreakpointDto | IDataBreakpointDto> {
return bps.map(bp => {
if ('name' in bp) {
const fbp = <IFunctionBreakpoint>bp;
Expand All @@ -307,6 +311,19 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
logMessage: fbp.logMessage,
functionName: fbp.name
};
} else if ('dataId' in bp) {
const dbp = <IDataBreakpoint>bp;
return <IDataBreakpointDto>{
type: 'data',
id: dbp.getId(),
dataId: dbp.dataId,
enabled: dbp.enabled,
condition: dbp.condition,
hitCondition: dbp.hitCondition,
logMessage: dbp.logMessage,
label: dbp.label,
canPersist: dbp.canPersist
};
} else {
const sbp = <IBreakpoint>bp;
return <ISourceBreakpointDto>{
Expand Down
15 changes: 11 additions & 4 deletions src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -715,8 +715,8 @@ export interface MainThreadDebugServiceShape extends IDisposable {
$customDebugAdapterRequest(id: DebugSessionUUID, command: string, args: any): Promise<any>;
$appendDebugConsole(value: string): void;
$startBreakpointEvents(): void;
$registerBreakpoints(breakpoints: Array<ISourceMultiBreakpointDto | IFunctionBreakpointDto>): Promise<void>;
$unregisterBreakpoints(breakpointIds: string[], functionBreakpointIds: string[]): Promise<void>;
$registerBreakpoints(breakpoints: Array<ISourceMultiBreakpointDto | IFunctionBreakpointDto | IDataBreakpointDto>): Promise<void>;
$unregisterBreakpoints(breakpointIds: string[], functionBreakpointIds: string[], dataBreakpointIds: string[]): Promise<void>;
}

export interface IOpenUriOptions {
Expand Down Expand Up @@ -1198,6 +1198,13 @@ export interface IFunctionBreakpointDto extends IBreakpointDto {
functionName: string;
}

export interface IDataBreakpointDto extends IBreakpointDto {
type: 'data';
dataId: string;
canPersist: boolean;
label: string;
}

export interface ISourceBreakpointDto extends IBreakpointDto {
type: 'source';
uri: UriComponents;
Expand All @@ -1206,9 +1213,9 @@ export interface ISourceBreakpointDto extends IBreakpointDto {
}

export interface IBreakpointsDeltaDto {
added?: Array<ISourceBreakpointDto | IFunctionBreakpointDto>;
added?: Array<ISourceBreakpointDto | IFunctionBreakpointDto | IDataBreakpointDto>;
removed?: string[];
changed?: Array<ISourceBreakpointDto | IFunctionBreakpointDto>;
changed?: Array<ISourceBreakpointDto | IFunctionBreakpointDto | IDataBreakpointDto>;
}

export interface ISourceMultiBreakpointDto {
Expand Down
18 changes: 18 additions & 0 deletions src/vs/workbench/api/common/extHostTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2166,6 +2166,24 @@ export class FunctionBreakpoint extends Breakpoint {
}
}

@es5ClassCompat
export class DataBreakpoint extends Breakpoint {
readonly label: string;
readonly dataId: string;
readonly canPersist: boolean;

constructor(label: string, dataId: string, canPersist: boolean, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string) {
super(enabled, condition, hitCondition, logMessage);
if (!dataId) {
throw illegalArgument('dataId');
}
this.label = label;
this.dataId = dataId;
this.canPersist = canPersist;
}
}


@es5ClassCompat
export class DebugAdapterExecutable implements vscode.DebugAdapterExecutable {
readonly command: string;
Expand Down
7 changes: 5 additions & 2 deletions src/vs/workbench/api/node/extHostDebugService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto
} from 'vs/workbench/api/common/extHost.protocol';
import * as vscode from 'vscode';
import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable } from 'vs/workbench/api/common/extHostTypes';
import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable, DataBreakpoint } from 'vs/workbench/api/common/extHostTypes';
import { ExecutableDebugAdapter, SocketDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter';
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
Expand Down Expand Up @@ -248,7 +248,8 @@ export class ExtHostDebugService implements IExtHostDebugService, ExtHostDebugSe
// unregister with VS Code
const ids = breakpoints.filter(bp => bp instanceof SourceBreakpoint).map(bp => bp.id);
const fids = breakpoints.filter(bp => bp instanceof FunctionBreakpoint).map(bp => bp.id);
return this._debugServiceProxy.$unregisterBreakpoints(ids, fids);
const dids = breakpoints.filter(bp => bp instanceof DataBreakpoint).map(bp => bp.id);
return this._debugServiceProxy.$unregisterBreakpoints(ids, fids, dids);
}

public startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, parentSession?: vscode.DebugSession): Promise<boolean> {
Expand Down Expand Up @@ -554,6 +555,8 @@ export class ExtHostDebugService implements IExtHostDebugService, ExtHostDebugSe
let bp: vscode.Breakpoint;
if (bpd.type === 'function') {
bp = new FunctionBreakpoint(bpd.functionName, bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage);
} else if (bpd.type === 'data') {
bp = new DataBreakpoint(bpd.label, bpd.dataId, bpd.canPersist, bpd.enabled, bpd.hitCondition, bpd.condition, bpd.logMessage);
} else {
const uri = URI.revive(bpd.uri);
bp = new SourceBreakpoint(new Location(uri, new Position(bpd.line, bpd.character)), bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage);
Expand Down
90 changes: 82 additions & 8 deletions src/vs/workbench/contrib/debug/browser/breakpointsView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as resources from 'vs/base/common/resources';
import * as dom from 'vs/base/browser/dom';
import { IAction, Action } from 'vs/base/common/actions';
import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINTS_FOCUSED, EDITOR_CONTRIBUTION_ID, State, DEBUG_SCHEME, IFunctionBreakpoint, IExceptionBreakpoint, IEnablement, IDebugEditorContribution } from 'vs/workbench/contrib/debug/common/debug';
import { ExceptionBreakpoint, FunctionBreakpoint, Breakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
import { ExceptionBreakpoint, FunctionBreakpoint, Breakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
import { AddFunctionBreakpointAction, ToggleBreakpointsActivatedAction, RemoveAllBreakpointsAction, RemoveBreakpointAction, EnableAllBreakpointsAction, DisableAllBreakpointsAction, ReapplyBreakpointsAction } from 'vs/workbench/contrib/debug/browser/debugActions';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
Expand Down Expand Up @@ -74,6 +74,7 @@ export class BreakpointsView extends ViewletPanel {
this.instantiationService.createInstance(BreakpointsRenderer),
new ExceptionBreakpointsRenderer(this.debugService),
this.instantiationService.createInstance(FunctionBreakpointsRenderer),
this.instantiationService.createInstance(DataBreakpointsRenderer),
new FunctionBreakpointInputRenderer(this.debugService, this.contextViewService, this.themeService)
], {
identityProvider: { getId: (element: IEnablement) => element.getId() },
Expand All @@ -91,7 +92,7 @@ export class BreakpointsView extends ViewletPanel {

this._register(this.list.onContextMenu(this.onListContextMenu, this));

this._register(this.list.onDidOpen(e => {
this._register(this.list.onDidOpen(async e => {
let isSingleClick = false;
let isDoubleClick = false;
let isMiddleClick = false;
Expand All @@ -110,9 +111,11 @@ export class BreakpointsView extends ViewletPanel {

if (isMiddleClick) {
if (element instanceof Breakpoint) {
this.debugService.removeBreakpoints(element.getId());
await this.debugService.removeBreakpoints(element.getId());
} else if (element instanceof FunctionBreakpoint) {
this.debugService.removeFunctionBreakpoints(element.getId());
await this.debugService.removeFunctionBreakpoints(element.getId());
} else if (element instanceof DataBreakpoint) {
await this.debugService.removeDataBreakpoints(element.getId());
}
return;
}
Expand Down Expand Up @@ -222,14 +225,14 @@ export class BreakpointsView extends ViewletPanel {

private get elements(): IEnablement[] {
const model = this.debugService.getModel();
const elements = (<ReadonlyArray<IEnablement>>model.getExceptionBreakpoints()).concat(model.getFunctionBreakpoints()).concat(model.getBreakpoints());
const elements = (<ReadonlyArray<IEnablement>>model.getExceptionBreakpoints()).concat(model.getFunctionBreakpoints()).concat(model.getDataBreakpoints()).concat(model.getBreakpoints());

return elements;
}

private getExpandedBodySize(): number {
const model = this.debugService.getModel();
const length = model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getFunctionBreakpoints().length;
const length = model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getFunctionBreakpoints().length + model.getDataBreakpoints().length;
return Math.min(BreakpointsView.MAX_VISIBLE_FILES, length) * 22;
}
}
Expand Down Expand Up @@ -259,6 +262,9 @@ class BreakpointsDelegate implements IListVirtualDelegate<IEnablement> {
if (element instanceof ExceptionBreakpoint) {
return ExceptionBreakpointsRenderer.ID;
}
if (element instanceof DataBreakpoint) {
return DataBreakpointsRenderer.ID;
}

return '';
}
Expand Down Expand Up @@ -454,6 +460,61 @@ class FunctionBreakpointsRenderer implements IListRenderer<FunctionBreakpoint, I
}
}

class DataBreakpointsRenderer implements IListRenderer<DataBreakpoint, IBaseBreakpointWithIconTemplateData> {

constructor(
@IDebugService private readonly debugService: IDebugService
) {
// noop
}

static readonly ID = 'databreakpoints';

get templateId() {
return DataBreakpointsRenderer.ID;
}

renderTemplate(container: HTMLElement): IBaseBreakpointWithIconTemplateData {
const data: IBreakpointTemplateData = Object.create(null);
data.breakpoint = dom.append(container, $('.breakpoint'));

data.icon = $('.icon');
data.checkbox = createCheckbox();
data.toDispose = [];
data.toDispose.push(dom.addStandardDisposableListener(data.checkbox, 'change', (e) => {
this.debugService.enableOrDisableBreakpoints(!data.context.enabled, data.context);
}));

dom.append(data.breakpoint, data.icon);
dom.append(data.breakpoint, data.checkbox);

data.name = dom.append(data.breakpoint, $('span.name'));

return data;
}

renderElement(dataBreakpoint: DataBreakpoint, index: number, data: IBaseBreakpointWithIconTemplateData): void {
data.context = dataBreakpoint;
data.name.textContent = dataBreakpoint.label;
const { className, message } = getBreakpointMessageAndClassName(this.debugService, dataBreakpoint);
data.icon.className = className + ' icon';
data.icon.title = message ? message : '';
data.checkbox.checked = dataBreakpoint.enabled;
data.breakpoint.title = dataBreakpoint.label;

// Mark function breakpoints as disabled if deactivated or if debug type does not support them #9099
const session = this.debugService.getViewModel().focusedSession;
dom.toggleClass(data.breakpoint, 'disabled', (session && !session.capabilities.supportsDataBreakpoints) || !this.debugService.getModel().areBreakpointsActivated());
if (session && !session.capabilities.supportsDataBreakpoints) {
data.breakpoint.title = nls.localize('dataBreakpointsNotSupported', "Data breakpoints are not supported by this debug type");
}
}

disposeTemplate(templateData: IBaseBreakpointWithIconTemplateData): void {
dispose(templateData.toDispose);
}
}

class FunctionBreakpointInputRenderer implements IListRenderer<IFunctionBreakpoint, IInputTemplateData> {

constructor(
Expand Down Expand Up @@ -572,7 +633,7 @@ export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolea
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
}

export function getBreakpointMessageAndClassName(debugService: IDebugService, breakpoint: IBreakpoint | FunctionBreakpoint): { message?: string, className: string } {
export function getBreakpointMessageAndClassName(debugService: IDebugService, breakpoint: IBreakpoint | FunctionBreakpoint | DataBreakpoint): { message?: string, className: string } {
const state = debugService.state;
const debugActive = state === State.Running || state === State.Stopped;

Expand All @@ -584,7 +645,7 @@ export function getBreakpointMessageAndClassName(debugService: IDebugService, br
}

const appendMessage = (text: string): string => {
return !(breakpoint instanceof FunctionBreakpoint) && breakpoint.message ? text.concat(', ' + breakpoint.message) : text;
return !(breakpoint instanceof FunctionBreakpoint) && !(breakpoint instanceof DataBreakpoint) && breakpoint.message ? text.concat(', ' + breakpoint.message) : text;
};
if (debugActive && !breakpoint.verified) {
return {
Expand All @@ -607,6 +668,19 @@ export function getBreakpointMessageAndClassName(debugService: IDebugService, br
};
}

if (breakpoint instanceof DataBreakpoint) {
if (session && !session.capabilities.supportsDataBreakpoints) {
return {
className: 'debug-data-breakpoint-unverified',
message: nls.localize('dataBreakpointUnsupported', "Data breakpoints not supported by this debug type"),
};
}

return {
className: 'debug-data-breakpoint',
};
}

if (breakpoint.logMessage || breakpoint.condition || breakpoint.hitCondition) {
const messages: string[] = [];
if (breakpoint.logMessage) {
Expand Down
6 changes: 3 additions & 3 deletions src/vs/workbench/contrib/debug/browser/debugActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import * as lifecycle from 'vs/base/common/lifecycle';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IDebugService, State, IEnablement, IBreakpoint, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
import { Variable, Breakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
import { Variable, Breakpoint, FunctionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { INotificationService } from 'vs/platform/notification/common/notification';
Expand Down Expand Up @@ -191,7 +191,7 @@ export class RemoveBreakpointAction extends AbstractDebugAction {

public run(breakpoint: IBreakpoint): Promise<any> {
return breakpoint instanceof Breakpoint ? this.debugService.removeBreakpoints(breakpoint.getId())
: this.debugService.removeFunctionBreakpoints(breakpoint.getId());
: breakpoint instanceof FunctionBreakpoint ? this.debugService.removeFunctionBreakpoints(breakpoint.getId()) : this.debugService.removeDataBreakpoints(breakpoint.getId());
}
}

Expand All @@ -205,7 +205,7 @@ export class RemoveAllBreakpointsAction extends AbstractDebugAction {
}

public run(): Promise<any> {
return Promise.all([this.debugService.removeBreakpoints(), this.debugService.removeFunctionBreakpoints()]);
return Promise.all([this.debugService.removeBreakpoints(), this.debugService.removeFunctionBreakpoints(), this.debugService.removeDataBreakpoints()]);
}

protected isEnabled(state: State): boolean {
Expand Down
4 changes: 3 additions & 1 deletion src/vs/workbench/contrib/debug/browser/debugCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co
import { IListService } from 'vs/platform/list/browser/listService';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_EXPRESSION_SELECTED, CONTEXT_BREAKPOINT_SELECTED, IConfig, IStackFrame, IThread, IDebugSession, CONTEXT_DEBUG_STATE, REPL_ID, IDebugConfiguration, CONTEXT_JUMP_TO_CURSOR_SUPPORTED } from 'vs/workbench/contrib/debug/common/debug';
import { Expression, Variable, Breakpoint, FunctionBreakpoint, Thread } from 'vs/workbench/contrib/debug/common/debugModel';
import { Expression, Variable, Breakpoint, FunctionBreakpoint, Thread, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
Expand Down Expand Up @@ -410,6 +410,8 @@ export function registerCommands(): void {
debugService.removeBreakpoints(element.getId());
} else if (element instanceof FunctionBreakpoint) {
debugService.removeFunctionBreakpoints(element.getId());
} else if (element instanceof DataBreakpoint) {
debugService.removeDataBreakpoints(element.getId());
}
}
}
Expand Down
Loading

0 comments on commit a7cc79a

Please sign in to comment.