Skip to content

Commit c14d512

Browse files
authored
Merge pull request #764 from isc-bsaviano/conditional-breakpoints
Add support for conditional breakpoints
2 parents 81a1304 + 046c18b commit c14d512

File tree

2 files changed

+122
-20
lines changed

2 files changed

+122
-20
lines changed

src/debug/debugSession.ts

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@ export class ObjectScriptDebugSession extends LoggingDebugSession {
110110
supportsConfigurationDoneRequest: true,
111111
supportsEvaluateForHovers: true,
112112
supportsSetVariable: true,
113-
supportsConditionalBreakpoints: false, // TODO
113+
supportsConditionalBreakpoints: true,
114+
supportsHitConditionalBreakpoints: true,
114115
supportsStepBack: false,
115116
supportsDataBreakpoints: true,
116117
};
@@ -266,24 +267,54 @@ export class ObjectScriptDebugSession extends LoggingDebugSession {
266267
xdebugBreakpoints = await Promise.all(
267268
args.breakpoints.map(async (breakpoint) => {
268269
const line = breakpoint.line;
269-
if (breakpoint.condition) {
270-
return new xdebug.ConditionalBreakpoint(breakpoint.condition, fileUri, line);
271-
} else if (fileName.endsWith("cls")) {
270+
if (fileName.endsWith("cls")) {
272271
return await vscode.workspace.openTextDocument(uri).then((document) => {
273272
const methodMatchPattern = new RegExp(`^(?:Class)?Method ([^(]+)(?=[( ])`, "i");
274273
for (let i = line; line > 0; i--) {
275274
const lineOfCode = document.lineAt(i).text;
276275
const methodMatch = lineOfCode.match(methodMatchPattern);
277276
if (methodMatch) {
278277
const [, methodName] = methodMatch;
279-
return new xdebug.ClassLineBreakpoint(fileUri, line, methodName, line - i - 2);
278+
if (breakpoint.condition) {
279+
return new xdebug.ClassConditionalBreakpoint(
280+
breakpoint.condition,
281+
fileUri,
282+
line,
283+
methodName,
284+
line - i - 2,
285+
breakpoint.hitCondition
286+
);
287+
} else {
288+
return new xdebug.ClassLineBreakpoint(
289+
fileUri,
290+
line,
291+
methodName,
292+
line - i - 2,
293+
breakpoint.hitCondition
294+
);
295+
}
280296
}
281297
}
282298
});
283299
} else if (filePath.endsWith("mac") || filePath.endsWith("int")) {
284-
return new xdebug.RoutineLineBreakpoint(fileUri, line, "", line - 1);
300+
if (breakpoint.condition) {
301+
return new xdebug.RoutineConditionalBreakpoint(
302+
breakpoint.condition,
303+
fileUri,
304+
line,
305+
"",
306+
line - 1,
307+
breakpoint.hitCondition
308+
);
309+
} else {
310+
return new xdebug.RoutineLineBreakpoint(fileUri, line, "", line - 1, breakpoint.hitCondition);
311+
}
285312
} else {
286-
return new xdebug.LineBreakpoint(fileUri, line);
313+
if (breakpoint.condition) {
314+
return new xdebug.ConditionalBreakpoint(breakpoint.condition, fileUri, line, breakpoint.hitCondition);
315+
} else {
316+
return new xdebug.LineBreakpoint(fileUri, line, breakpoint.hitCondition);
317+
}
287318
}
288319
})
289320
);
@@ -292,8 +323,17 @@ export class ObjectScriptDebugSession extends LoggingDebugSession {
292323
await Promise.all(
293324
xdebugBreakpoints.map(async (breakpoint, index) => {
294325
try {
295-
await this._connection.sendBreakpointSetCommand(breakpoint);
296-
vscodeBreakpoints[index] = { verified: true, line: breakpoint.line };
326+
if (breakpoint.hitCondition && !/^[1-9]\d*$/.test(breakpoint.hitCondition)) {
327+
// The user-defined hitCondition wasn't a positive integer
328+
vscodeBreakpoints[index] = {
329+
verified: false,
330+
line: breakpoint.line,
331+
message: "Hit Count must be a positive integer",
332+
};
333+
} else {
334+
await this._connection.sendBreakpointSetCommand(breakpoint);
335+
vscodeBreakpoints[index] = { verified: true, line: breakpoint.line };
336+
}
297337
} catch (error) {
298338
vscodeBreakpoints[index] = {
299339
verified: false,

src/debug/xdebugConnection.ts

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,12 @@ export abstract class Breakpoint {
143143
public state: BreakpointState;
144144
/** The connection this breakpoint is set on */
145145
public connection: Connection;
146+
/** The value of the `hitCondition` property of the input `DebugProtocol.SourceBreakpoint` */
147+
public hitCondition?: string;
146148
/** Constructs a breakpoint object from an XML node from a XDebug response */
147149
public constructor(breakpointNode: Element, connection: Connection);
148150
/** To create a new breakpoint in derived classes */
149-
public constructor(type: BreakpointType);
151+
public constructor(type: BreakpointType, hitCondition?: string);
150152
public constructor(...rest: any[]) {
151153
if (typeof rest[0] === "object") {
152154
// from XML
@@ -157,6 +159,7 @@ export abstract class Breakpoint {
157159
this.state = breakpointNode.getAttribute("state") as BreakpointState;
158160
} else {
159161
this.type = rest[0];
162+
this.hitCondition = rest[1].trim();
160163
this.state = "enabled";
161164
}
162165
}
@@ -175,7 +178,7 @@ export class LineBreakpoint extends Breakpoint {
175178
/** constructs a line breakpoint from an XML node */
176179
public constructor(breakpointNode: Element, connection: Connection);
177180
/** contructs a line breakpoint for passing to sendSetBreakpointCommand */
178-
public constructor(fileUri: string, line: number);
181+
public constructor(fileUri: string, line: number, hitCondition?: string);
179182
public constructor(...rest: any[]) {
180183
if (typeof rest[0] === "object") {
181184
const breakpointNode: Element = rest[0];
@@ -185,7 +188,7 @@ export class LineBreakpoint extends Breakpoint {
185188
this.fileUri = breakpointNode.getAttribute("filename");
186189
} else {
187190
// construct from arguments
188-
super("line");
191+
super("line", rest[2]);
189192
this.fileUri = rest[0];
190193
this.line = rest[1];
191194
}
@@ -197,7 +200,7 @@ export class ClassLineBreakpoint extends LineBreakpoint {
197200
public methodOffset: number;
198201

199202
/** contructs a line breakpoint for passing to sendSetBreakpointCommand */
200-
public constructor(fileUri: string, line: number, method: string, methodOffset: number);
203+
public constructor(fileUri: string, line: number, method: string, methodOffset: number, hitCondition?: string);
201204
public constructor(...rest: any[]) {
202205
if (typeof rest[0] === "object") {
203206
const breakpointNode: Element = rest[0];
@@ -206,7 +209,7 @@ export class ClassLineBreakpoint extends LineBreakpoint {
206209
this.line = parseInt(breakpointNode.getAttribute("lineno"), 10);
207210
this.fileUri = breakpointNode.getAttribute("filename");
208211
} else {
209-
super(rest[0], rest[1]);
212+
super(rest[0], rest[1], rest[4]);
210213
this.method = rest[2];
211214
this.methodOffset = rest[3];
212215
}
@@ -218,7 +221,7 @@ export class RoutineLineBreakpoint extends LineBreakpoint {
218221
public methodOffset: number;
219222

220223
/** contructs a line breakpoint for passing to sendSetBreakpointCommand */
221-
public constructor(fileUri: string, line: number, method: string, methodOffset: number);
224+
public constructor(fileUri: string, line: number, method: string, methodOffset: number, hitCondition?: string);
222225
public constructor(...rest: any[]) {
223226
if (typeof rest[0] === "object") {
224227
const breakpointNode: Element = rest[0];
@@ -227,7 +230,7 @@ export class RoutineLineBreakpoint extends LineBreakpoint {
227230
this.line = parseInt(breakpointNode.getAttribute("lineno"), 10);
228231
this.fileUri = breakpointNode.getAttribute("filename");
229232
} else {
230-
super(rest[0], rest[1]);
233+
super(rest[0], rest[1], rest[4]);
231234
this.method = rest[2];
232235
this.methodOffset = rest[3];
233236
}
@@ -245,7 +248,7 @@ export class ConditionalBreakpoint extends Breakpoint {
245248
/** Constructs a breakpoint object from an XML node from a XDebug response */
246249
public constructor(breakpointNode: Element, connection: Connection);
247250
/** Contructs a breakpoint object for passing to sendSetBreakpointCommand */
248-
public constructor(expression: string, fileUri: string, line?: number);
251+
public constructor(expression: string, fileUri: string, line?: number, hitCondition?: string);
249252
public constructor(...rest: any[]) {
250253
if (typeof rest[0] === "object") {
251254
// from XML
@@ -255,14 +258,68 @@ export class ConditionalBreakpoint extends Breakpoint {
255258
this.expression = breakpointNode.getAttribute("expression"); // Base64 encoded?
256259
} else {
257260
// from arguments
258-
super("conditional");
261+
super("conditional", rest[3]);
259262
this.expression = rest[0];
260263
this.fileUri = rest[1];
261264
this.line = rest[2];
262265
}
263266
}
264267
}
265268

269+
export class ClassConditionalBreakpoint extends ConditionalBreakpoint {
270+
public method: string;
271+
public methodOffset: number;
272+
273+
/** contructs a conditional breakpoint for passing to sendSetBreakpointCommand */
274+
public constructor(
275+
expression: string,
276+
fileUri: string,
277+
line: number,
278+
method: string,
279+
methodOffset: number,
280+
hitCondition?: string
281+
);
282+
public constructor(...rest: any[]) {
283+
if (typeof rest[0] === "object") {
284+
const breakpointNode: Element = rest[0];
285+
const connection: Connection = rest[1];
286+
super(breakpointNode, connection);
287+
this.expression = breakpointNode.getAttribute("expression"); // Base64 encoded?
288+
} else {
289+
super(rest[0], rest[1], rest[2], rest[5]);
290+
this.method = rest[3];
291+
this.methodOffset = rest[4];
292+
}
293+
}
294+
}
295+
296+
export class RoutineConditionalBreakpoint extends ConditionalBreakpoint {
297+
public method: string;
298+
public methodOffset: number;
299+
300+
/** contructs a conditional breakpoint for passing to sendSetBreakpointCommand */
301+
public constructor(
302+
expression: string,
303+
fileUri: string,
304+
line: number,
305+
method: string,
306+
methodOffset: number,
307+
hitCondition?: string
308+
);
309+
public constructor(...rest: any[]) {
310+
if (typeof rest[0] === "object") {
311+
const breakpointNode: Element = rest[0];
312+
const connection: Connection = rest[1];
313+
super(breakpointNode, connection);
314+
this.expression = breakpointNode.getAttribute("expression"); // Base64 encoded?
315+
} else {
316+
super(rest[0], rest[1], rest[2], rest[5]);
317+
this.method = rest[3];
318+
this.methodOffset = rest[4];
319+
}
320+
}
321+
}
322+
266323
/** class for watch breakpoints. Returned from a breakpoint_list or passed to sendBreakpointSetCommand */
267324
export class Watchpoint extends Breakpoint {
268325
/** The variable to watch */
@@ -782,8 +839,10 @@ export class Connection extends DbgpConnection {
782839
}
783840
} else if (breakpoint instanceof ConditionalBreakpoint) {
784841
args += ` -f ${breakpoint.fileUri}`;
785-
if (typeof breakpoint.line === "number") {
786-
args += ` -n ${breakpoint.line}`;
842+
if (breakpoint instanceof ClassConditionalBreakpoint) {
843+
args += ` -m ${breakpoint.method} -n ${breakpoint.methodOffset}`;
844+
} else if (breakpoint instanceof RoutineConditionalBreakpoint) {
845+
args += ` -n ${breakpoint.methodOffset}`;
787846
}
788847
data = breakpoint.expression;
789848
} else if (breakpoint instanceof Watchpoint) {
@@ -795,6 +854,9 @@ export class Connection extends DbgpConnection {
795854
args += ` -m PLACEHOLDER`;
796855
args += ` -n PLACEHOLDER`;
797856
}
857+
if (breakpoint.hitCondition) {
858+
args += ` -h ${breakpoint.hitCondition}`;
859+
}
798860
return new BreakpointSetResponse(await this._enqueueCommand("breakpoint_set", args, data), this);
799861
}
800862

0 commit comments

Comments
 (0)