Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend support for data breakpoints in VS Code API #226735

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 87 additions & 1 deletion extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import * as assert from 'assert';
import { basename } from 'path';
import { commands, debug, Disposable, FunctionBreakpoint, window, workspace } from 'vscode';
import { commands, DataBreakpoint, debug, Disposable, FunctionBreakpoint, window, workspace } from 'vscode';
import { assertNoRpc, createRandomFile, disposeAll } from '../utils';

suite('vscode API - debug', function () {
Expand Down Expand Up @@ -60,6 +60,92 @@ suite('vscode API - debug', function () {
assert.strictEqual(functionBreakpoint.functionName, 'func');
});


test('data breakpoint - dataId', async function () {
debug.addBreakpoints([new DataBreakpoint({ type: 'variable', dataId: 'dataId' }, 'readWrite', false, 'data', false, 'condition', 'hitCondition', 'logMessage')]);
const variableDbp = debug.breakpoints[debug.breakpoints.length - 1] as DataBreakpoint;
assert.strictEqual(variableDbp.condition, 'condition');
assert.strictEqual(variableDbp.hitCondition, 'hitCondition');
assert.strictEqual(variableDbp.logMessage, 'logMessage');
assert.strictEqual(variableDbp.enabled, false);
assert.strictEqual(variableDbp.label, 'data');
assert.strictEqual(variableDbp.source.type, 'variable');
assert.strictEqual(variableDbp.source.dataId, 'dataId');
assert.strictEqual(variableDbp.canPersist, false);
assert.strictEqual(variableDbp.accessType, 'readWrite');
});

test('data breakpoint - variable', async function () {
debug.addBreakpoints([new DataBreakpoint('dataId', 'readWrite', false, 'data', false, 'condition', 'hitCondition', 'logMessage')]);
const dataIdDbp = debug.breakpoints[debug.breakpoints.length - 1] as DataBreakpoint;
assert.strictEqual(dataIdDbp.condition, 'condition');
assert.strictEqual(dataIdDbp.hitCondition, 'hitCondition');
assert.strictEqual(dataIdDbp.logMessage, 'logMessage');
assert.strictEqual(dataIdDbp.enabled, false);
assert.strictEqual(dataIdDbp.label, 'data');
assert.strictEqual(dataIdDbp.source.type, 'variable');
assert.strictEqual(dataIdDbp.source.dataId, 'dataId');
assert.strictEqual(dataIdDbp.canPersist, false);
assert.strictEqual(dataIdDbp.accessType, 'readWrite');
});

test('data breakpoint - address', async function () {
debug.addBreakpoints([new DataBreakpoint({ type: 'address', address: '0x00000', bytes: 4 }, 'readWrite', false, 'data', false, 'condition', 'hitCondition', 'logMessage')]);
const addressDbp = debug.breakpoints[debug.breakpoints.length - 1] as DataBreakpoint;
assert.strictEqual(addressDbp.condition, 'condition');
assert.strictEqual(addressDbp.hitCondition, 'hitCondition');
assert.strictEqual(addressDbp.logMessage, 'logMessage');
assert.strictEqual(addressDbp.enabled, false);
assert.strictEqual(addressDbp.label, 'data');
assert.strictEqual(addressDbp.source.type, 'address');
assert.strictEqual(addressDbp.source.address, '0x00000');
assert.strictEqual(addressDbp.source.bytes, 4);
assert.strictEqual(addressDbp.canPersist, false);
assert.strictEqual(addressDbp.accessType, 'readWrite');
});

test('data breakpoint - expression', async function () {
debug.addBreakpoints([new DataBreakpoint({ type: 'expression', expression: 'i' }, 'readWrite', false, 'data', false, 'condition', 'hitCondition', 'logMessage')]);
const dynamicVariableDbp = debug.breakpoints[debug.breakpoints.length - 1] as DataBreakpoint;
assert.strictEqual(dynamicVariableDbp.condition, 'condition');
assert.strictEqual(dynamicVariableDbp.hitCondition, 'hitCondition');
assert.strictEqual(dynamicVariableDbp.logMessage, 'logMessage');
assert.strictEqual(dynamicVariableDbp.enabled, false);
assert.strictEqual(dynamicVariableDbp.label, 'data');
assert.strictEqual(dynamicVariableDbp.source.type, 'expression');
assert.strictEqual(dynamicVariableDbp.source.expression, 'i');
assert.strictEqual(dynamicVariableDbp.canPersist, false);
assert.strictEqual(dynamicVariableDbp.accessType, 'readWrite');
});

test('data breakpoint - scoped', async function () {
debug.addBreakpoints([new DataBreakpoint({ type: 'scoped', expression: 'exp()', frameId: 1 }, 'readWrite', false, 'data', false, 'condition', 'hitCondition', 'logMessage')]);
const scopedExpression = debug.breakpoints[debug.breakpoints.length - 1] as DataBreakpoint;
assert.strictEqual(scopedExpression.condition, 'condition');
assert.strictEqual(scopedExpression.hitCondition, 'hitCondition');
assert.strictEqual(scopedExpression.logMessage, 'logMessage');
assert.strictEqual(scopedExpression.enabled, false);
assert.strictEqual(scopedExpression.label, 'data');
assert.strictEqual(scopedExpression.source.type, 'scoped');
assert.strictEqual(scopedExpression.source.frameId, 1);
assert.strictEqual(scopedExpression.source.expression, 'exp()');
assert.strictEqual(scopedExpression.canPersist, false);
assert.strictEqual(scopedExpression.accessType, 'readWrite');

debug.addBreakpoints([new DataBreakpoint({ type: 'scoped', variable: 'var', variablesReference: 1 }, 'readWrite', false, 'data', false, 'condition', 'hitCondition', 'logMessage')]);
const scopedVariable = debug.breakpoints[debug.breakpoints.length - 1] as DataBreakpoint;
assert.strictEqual(scopedVariable.condition, 'condition');
assert.strictEqual(scopedVariable.hitCondition, 'hitCondition');
assert.strictEqual(scopedVariable.logMessage, 'logMessage');
assert.strictEqual(scopedVariable.enabled, false);
assert.strictEqual(scopedVariable.label, 'data');
assert.strictEqual(scopedVariable.source.type, 'scoped');
assert.strictEqual(scopedVariable.source.variablesReference, 1);
assert.strictEqual(scopedVariable.source.variable, 'var');
assert.strictEqual(scopedVariable.canPersist, false);
assert.strictEqual(scopedVariable.accessType, 'readWrite');
});

test('start debugging', async function () {
let stoppedEvents = 0;
let variablesReceived: () => void;
Expand Down
3 changes: 3 additions & 0 deletions src/vs/platform/extensions/common/extensionsApiProposals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ const _allApiProposals = {
customEditorMove: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.customEditorMove.d.ts',
},
debugDataBreakpoints: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.debugDataBreakpoints.d.ts',
},
debugVisualization: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.debugVisualization.d.ts',
},
Expand Down
27 changes: 22 additions & 5 deletions src/vs/workbench/api/browser/mainThreadDebugService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
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) {
if (bps.length > 0 || fbps.length > 0 || dbps.length > 0) {
this._proxy.$acceptBreakpointsDelta({
added: this.convertToDto(bps).concat(this.convertToDto(fbps)).concat(this.convertToDto(dbps))
});
Expand Down Expand Up @@ -234,10 +234,19 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
} else if (dto.type === 'data') {
this.debugService.addDataBreakpoint({
description: dto.label,
src: { type: DataBreakpointSetType.Variable, dataId: dto.dataId },
src: dto.source.type === 'variable' ? { type: DataBreakpointSetType.Variable, dataId: dto.source.dataId }
: dto.source.type === 'address' ? { type: DataBreakpointSetType.Address, address: dto.source.address, bytes: dto.source.bytes }
: dto.source.type === 'expression' ? { type: DataBreakpointSetType.Expression, expression: dto.source.expression }
: dto.source.frameId ? { type: DataBreakpointSetType.Scoped, expression: dto.source.expression, frameId: dto.source.frameId }
: dto.source.variablesReference ? { type: DataBreakpointSetType.Scoped, variable: dto.source.variable, variablesReference: dto.source.variablesReference }
: { type: DataBreakpointSetType.Variable, dataId: '' }, // should not happen
condition: dto.condition,
enabled: dto.enabled,
hitCondition: dto.hitCondition,
canPersist: dto.canPersist,
accessTypes: dto.accessTypes,
accessType: dto.accessType,
logMessage: dto.logMessage,
mode: dto.mode
});
}
Expand Down Expand Up @@ -450,21 +459,28 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
condition: fbp.condition,
hitCondition: fbp.hitCondition,
logMessage: fbp.logMessage,
functionName: fbp.name
functionName: fbp.name,
mode: fbp.mode
} satisfies IFunctionBreakpointDto;
} else if ('src' in bp) {
const dbp: IDataBreakpoint = bp;
return {
type: 'data',
id: dbp.getId(),
dataId: dbp.src.type === DataBreakpointSetType.Variable ? dbp.src.dataId : dbp.src.address,
source: dbp.src.type === DataBreakpointSetType.Variable ? { type: 'variable', dataId: dbp.src.dataId }
: dbp.src.type === DataBreakpointSetType.Address ? { type: 'address', address: dbp.src.address, bytes: dbp.src.bytes }
: dbp.src.type === DataBreakpointSetType.Expression ? { type: 'expression', expression: dbp.src.expression }
: dbp.src.frameId ? { type: 'scoped', expression: dbp.src.expression, frameId: dbp.src.frameId }
: dbp.src.variablesReference ? { type: 'scoped', variable: dbp.src.variable, variablesReference: dbp.src.variablesReference }
: { type: 'variable', dataId: '' }, // should not happen
enabled: dbp.enabled,
condition: dbp.condition,
hitCondition: dbp.hitCondition,
logMessage: dbp.logMessage,
accessType: dbp.accessType,
label: dbp.description,
canPersist: dbp.canPersist
canPersist: dbp.canPersist,
mode: dbp.mode
} satisfies IDataBreakpointDto;
} else if ('uri' in bp) {
const sbp: IBreakpoint = bp;
Expand All @@ -478,6 +494,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
uri: sbp.uri,
line: sbp.lineNumber > 0 ? sbp.lineNumber - 1 : 0,
character: (typeof sbp.column === 'number' && sbp.column > 0) ? sbp.column - 1 : 0,
mode: sbp.mode
} satisfies ISourceBreakpointDto;
} else {
return undefined;
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/api/common/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1562,6 +1562,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
CompletionTriggerKind: extHostTypes.CompletionTriggerKind,
ConfigurationTarget: extHostTypes.ConfigurationTarget,
CustomExecution: extHostTypes.CustomExecution,
DataBreakpoint: extHostTypes.DataBreakpoint,
DebugAdapterExecutable: extHostTypes.DebugAdapterExecutable,
DebugAdapterInlineImplementation: extHostTypes.DebugAdapterInlineImplementation,
DebugAdapterNamedPipeServer: extHostTypes.DebugAdapterNamedPipeServer,
Expand Down
8 changes: 4 additions & 4 deletions src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ import { IFileQueryBuilderOptions, ITextQueryBuilderOptions } from '../../servic
import * as search from '../../services/search/common/search.js';
import { TextSearchCompleteMessage } from '../../services/search/common/searchExtTypes.js';
import { ISaveProfileResult } from '../../services/userDataProfile/common/userDataProfile.js';
import type { TerminalShellExecutionCommandLineConfidence } from 'vscode';
import type * as vscode from 'vscode';

export interface IWorkspaceData extends IStaticWorkspaceData {
folders: { uri: UriComponents; name: string; index: number }[];
Expand Down Expand Up @@ -2338,8 +2338,8 @@ export interface ExtHostTerminalServiceShape {

export interface ExtHostTerminalShellIntegrationShape {
$shellIntegrationChange(instanceId: number): void;
$shellExecutionStart(instanceId: number, commandLineValue: string, commandLineConfidence: TerminalShellExecutionCommandLineConfidence, isTrusted: boolean, cwd: UriComponents | undefined): void;
$shellExecutionEnd(instanceId: number, commandLineValue: string, commandLineConfidence: TerminalShellExecutionCommandLineConfidence, isTrusted: boolean, exitCode: number | undefined): void;
$shellExecutionStart(instanceId: number, commandLineValue: string, commandLineConfidence: vscode.TerminalShellExecutionCommandLineConfidence, isTrusted: boolean, cwd: UriComponents | undefined): void;
$shellExecutionEnd(instanceId: number, commandLineValue: string, commandLineConfidence: vscode.TerminalShellExecutionCommandLineConfidence, isTrusted: boolean, exitCode: number | undefined): void;
$shellExecutionData(instanceId: number, data: string): void;
$cwdChange(instanceId: number, cwd: UriComponents | undefined): void;
$closeTerminal(instanceId: number): void;
Expand Down Expand Up @@ -2394,7 +2394,7 @@ export interface IFunctionBreakpointDto extends IBreakpointDto {

export interface IDataBreakpointDto extends IBreakpointDto {
type: 'data';
dataId: string;
source: vscode.DataBreakpointSource;
canPersist: boolean;
label: string;
accessTypes?: DebugProtocol.DataBreakpointAccessType[];
Expand Down
38 changes: 33 additions & 5 deletions src/vs/workbench/api/common/extHostDebugService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ExtensionIdentifier, IExtensionDescription } from '../../../platform/ex
import { createDecorator } from '../../../platform/instantiation/common/instantiation.js';
import { ISignService } from '../../../platform/sign/common/sign.js';
import { IWorkspaceFolder } from '../../../platform/workspace/common/workspace.js';
import { DebugSessionUUID, ExtHostDebugServiceShape, IBreakpointsDeltaDto, IThreadFocusDto, IStackFrameFocusDto, IDebugSessionDto, IFunctionBreakpointDto, ISourceMultiBreakpointDto, MainContext, MainThreadDebugServiceShape } from './extHost.protocol.js';
import { DebugSessionUUID, ExtHostDebugServiceShape, IBreakpointsDeltaDto, IThreadFocusDto, IStackFrameFocusDto, IDebugSessionDto, IFunctionBreakpointDto, ISourceMultiBreakpointDto, MainContext, MainThreadDebugServiceShape, IDataBreakpointDto } from './extHost.protocol.js';
import { IExtHostEditorTabs } from './extHostEditorTabs.js';
import { IExtHostExtensionService } from './extHostExtensionService.js';
import { IExtHostRpcService } from './extHostRpcService.js';
Expand All @@ -31,6 +31,7 @@ import { IExtHostCommands } from './extHostCommands.js';
import * as Convert from './extHostTypeConverters.js';
import { coalesce } from '../../../base/common/arrays.js';
import { IExtHostTesting } from './extHostTesting.js';
import { Mutable } from '../../../base/common/types.js';

export const IExtHostDebugService = createDecorator<IExtHostDebugService>('IExtHostDebugService');

Expand Down Expand Up @@ -411,7 +412,7 @@ export abstract class ExtHostDebugServiceBase extends DisposableCls implements I
this.fireBreakpointChanges(breakpoints, [], []);

// convert added breakpoints to DTOs
const dtos: Array<ISourceMultiBreakpointDto | IFunctionBreakpointDto> = [];
const dtos: Array<ISourceMultiBreakpointDto | IFunctionBreakpointDto | IDataBreakpointDto> = [];
const map = new Map<string, ISourceMultiBreakpointDto>();
for (const bp of breakpoints) {
if (bp instanceof SourceBreakpoint) {
Expand Down Expand Up @@ -446,6 +447,20 @@ export abstract class ExtHostDebugServiceBase extends DisposableCls implements I
functionName: bp.functionName,
mode: bp.mode,
});
} else if (bp instanceof DataBreakpoint) {
dtos.push({
type: 'data',
id: bp.id,
enabled: bp.enabled,
hitCondition: bp.hitCondition,
logMessage: bp.logMessage,
condition: bp.condition,
source: bp.source,
mode: bp.mode,
canPersist: bp.canPersist,
accessType: bp.accessType,
label: bp.label
});
}
}

Expand Down Expand Up @@ -728,7 +743,7 @@ export abstract class ExtHostDebugServiceBase extends DisposableCls implements I
if (bpd.type === 'function') {
bp = new FunctionBreakpoint(bpd.functionName, bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage, bpd.mode);
} else if (bpd.type === 'data') {
bp = new DataBreakpoint(bpd.label, bpd.dataId, bpd.canPersist, bpd.enabled, bpd.hitCondition, bpd.condition, bpd.logMessage, bpd.mode);
bp = new DataBreakpoint(bpd.source, bpd.accessType, bpd.canPersist, bpd.label, bpd.enabled, bpd.hitCondition, bpd.condition, bpd.logMessage, bpd.mode);
} 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, bpd.mode);
Expand Down Expand Up @@ -756,19 +771,32 @@ export abstract class ExtHostDebugServiceBase extends DisposableCls implements I
const bp = this._breakpoints.get(bpd.id);
if (bp) {
if (bp instanceof FunctionBreakpoint && bpd.type === 'function') {
const fbp = <any>bp;
const fbp = <Mutable<FunctionBreakpoint>>bp;
fbp.enabled = bpd.enabled;
fbp.condition = bpd.condition;
fbp.hitCondition = bpd.hitCondition;
fbp.logMessage = bpd.logMessage;
fbp.functionName = bpd.functionName;
fbp.mode = bpd.mode;
} else if (bp instanceof SourceBreakpoint && bpd.type === 'source') {
const sbp = <any>bp;
const sbp = <Mutable<SourceBreakpoint>>bp;
sbp.enabled = bpd.enabled;
sbp.condition = bpd.condition;
sbp.hitCondition = bpd.hitCondition;
sbp.logMessage = bpd.logMessage;
sbp.location = new Location(URI.revive(bpd.uri), new Position(bpd.line, bpd.character));
sbp.mode = bpd.mode;
} else if (bp instanceof DataBreakpoint && bpd.type === 'data') {
const dbp = <Mutable<DataBreakpoint>>bp;
dbp.enabled = bpd.enabled;
dbp.condition = bpd.condition;
dbp.hitCondition = bpd.hitCondition;
dbp.logMessage = bpd.logMessage;
dbp.label = bpd.label;
dbp.source = bpd.source;
dbp.canPersist = bpd.canPersist;
dbp.mode = bpd.mode;
dbp.accessType = bpd.accessType;
}
c.push(bp);
}
Expand Down
21 changes: 13 additions & 8 deletions src/vs/workbench/api/common/extHostTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3076,17 +3076,22 @@ export class FunctionBreakpoint extends Breakpoint {
@es5ClassCompat
export class DataBreakpoint extends Breakpoint {
readonly label: string;
readonly dataId: string;
readonly source: vscode.DataBreakpointSource;
readonly canPersist: boolean;
readonly accessType: vscode.DataBreakpointAccessType;

constructor(label: string, dataId: string, canPersist: boolean, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string, mode?: string) {
constructor(source: vscode.DataBreakpointSource | string, accessType: vscode.DataBreakpointAccessType, canPersist?: boolean, label?: string, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string, mode?: string) {
super(enabled, condition, hitCondition, logMessage, mode);
if (!dataId) {
throw illegalArgument('dataId');
}
this.label = label;
this.dataId = dataId;
this.canPersist = canPersist;
this.source = typeof source === 'string' ? { type: 'variable', dataId: source } : source;
this.accessType = accessType;
this.canPersist = canPersist ?? false;
this.label = label ? label
: this.source.type === 'variable' ? `DataId '${this.source.dataId}'`
: this.source.type === 'address' ? `Address '${this.source.address}${this.source.bytes ? `,${this.source.bytes}'` : ''}`
: this.source.type === 'expression' ? `Expression '${this.source.expression}'`
: this.source.frameId ? `Scoped '${this.source.expression}@${this.source.frameId}'`
: this.source.variablesReference ? `Scoped '${this.source.variable}@${this.source.variablesReference}'`
: `Unknown data breakpoint`;
}
}

Expand Down
Loading