Change:
Set some breakpoints Access from an extension- it's an empty array Try again - it's full Seems that we don't sync breakpoint into to the EH until the first call to one of the breakpoint properties do you have some context on this? It's a little confusing because I wouldn't expect sending breakpoint infos to be expensive, and I would expect to be able to access the breakpoints array immediately.
I do not have more context. I believe is the author of this code. I like your suggestion that we change this and actively sync the breakpoints into the EH (since it should not be expensive).
The observed behavior is by design: we want to avoid that all breakpoints get transferred to all active extensions all the time. But the API doc doesn't explain this...
Thanks is the worry that it's too much data/too expensive? What's the solution for extensions that need to access the list of breakpoints?
yes, potentially too much data and traffic for all extensions. What's the solution for extensions that need to access the list of breakpoints? adding a to the function of an extension will register the extension to receive live updates for . With this will always reflect the current set of breakpoints.
Thanks, I don't really understand that though, it doesn't seem like that much traffic/data, compared to something like the full text of open documents. If it's going to work this way, I should document it in But I might try doing some testing to remove this restriction because I think it's unexpected.
Verification steps: Create an extension that can log the value of Set a breakpoint in any file Then trigger your extension to check the length of the breakpoints array It should log the correct value on the first try- previously it would have been 0 on the first access, then the correct value afterwards

Discussion:
import * as assert from 'assert'; import { basename } from 'path'; import { commands, debug, Disposable, window, workspace } from 'vscode'; <del> import { assertNoRpc, disposeAll } from '../utils'; </del> <ins> import { assertNoRpc, createRandomFile, disposeAll } from '../utils'; </ins> suite('vscode API - debug', function () { teardown(assertNoRpc); <ins> test('breakpoints are available before accessing debug extension API', async () => { const file = await createRandomFile(undefined, undefined, '.js'); const doc = await workspace.openTextDocument(file); await window.showTextDocument(doc); await commands.executeCommand('editor.debug.action.toggleBreakpoint'); assert.strictEqual(debug.breakpoints.length, 1); await commands.executeCommand('editor.debug.action.toggleBreakpoint'); }); </ins> test('breakpoints', async function () { assert.strictEqual(debug.breakpoints.length, 0); let onDidChangeBreakpointsCounter = 0;

private readonly _proxy: ExtHostDebugServiceShape; private readonly _toDispose = new DisposableStore(); <del> private _breakpointEventsActive: boolean | undefined; </del> private readonly _debugAdapters: Map<number, ExtensionHostDebugAdapter>; private _debugAdaptersHandleCounter = 1; private readonly _debugConfigurationProviders: Map<number, IDebugConfigurationProvider>;

this._proxy.$acceptStackFrameFocus(dto); } })); <ins> this.sendBreakpointsAndListen(); } private sendBreakpointsAndListen(): void { // set up a handler to send more this._toDispose.add(this.debugService.getModel().onDidChangeBreakpoints(e => { // Ignore session only breakpoint events since they should only reflect in the UI if (e && !e.sessionOnly) { const delta: IBreakpointsDeltaDto = {}; if (e.added) { delta.added = this.convertToDto(e.added); } if (e.removed) { delta.removed = e.removed.map(x => x.getId()); } if (e.changed) { delta.changed = this.convertToDto(e.changed); } if (delta.added || delta.removed || delta.changed) { this._proxy.$acceptBreakpointsDelta(delta); } } })); // 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)).concat(this.convertToDto(dbps)) }); } </ins> } public dispose(): void {

this._toDispose.add(this.debugService.getAdapterManager().registerDebugAdapterFactory(debugTypes, this)); } <del> public $startBreakpointEvents(): void { if (!this._breakpointEventsActive) { this._breakpointEventsActive = true; // set up a handler to send more this._toDispose.add(this.debugService.getModel().onDidChangeBreakpoints(e => { // Ignore session only breakpoint events since they should only reflect in the UI if (e && !e.sessionOnly) { const delta: IBreakpointsDeltaDto = {}; if (e.added) { delta.added = this.convertToDto(e.added); } if (e.removed) { delta.removed = e.removed.map(x => x.getId()); } if (e.changed) { delta.changed = this.convertToDto(e.changed); } if (delta.added || delta.removed || delta.changed) { this._proxy.$acceptBreakpointsDelta(delta); } } })); // 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)).concat(this.convertToDto(dbps)) }); } } } </del> public $registerBreakpoints(DTOs: Array<ISourceMultiBreakpointDto | IFunctionBreakpointDto | IDataBreakpointDto>): Promise<void> { for (const dto of DTOs) {

$customDebugAdapterRequest(id: DebugSessionUUID, command: string, args: any): Promise<any>; $getDebugProtocolBreakpoint(id: DebugSessionUUID, breakpoinId: string): Promise<DebugProtocol.Breakpoint | undefined>; $appendDebugConsole(value: string): void; <del> $startBreakpointEvents(): void; </del> $registerBreakpoints(breakpoints: Array<ISourceMultiBreakpointDto | IFunctionBreakpointDto | IDataBreakpointDto>): Promise<void>; $unregisterBreakpoints(breakpointIds: string[], functionBreakpointIds: string[], dataBreakpointIds: string[]): Promise<void>; }

get activeDebugConsole(): vscode.DebugConsole { return this._activeDebugConsole.value; } private _breakpoints: Map<string, vscode.Breakpoint>; <del> private _breakpointEventsActive: boolean; </del> private readonly _onDidChangeBreakpoints: Emitter<vscode.BreakpointsChangeEvent>;

this._debugServiceProxy = extHostRpcService.getProxy(MainContext.MainThreadDebugService); <del> this._onDidChangeBreakpoints = new Emitter<vscode.BreakpointsChangeEvent>({ onWillAddFirstListener: () => { this.startBreakpoints(); } }); </del> <ins> this._onDidChangeBreakpoints = new Emitter<vscode.BreakpointsChangeEvent>(); </ins> this._onDidChangeStackFrameFocus = new Emitter<vscode.ThreadFocus | vscode.StackFrameFocus | undefined>(); this._activeDebugConsole = new ExtHostDebugConsole(this._debugServiceProxy); this._breakpoints = new Map<string, vscode.Breakpoint>(); <del> this._breakpointEventsActive = false; </del> this._extensionService.getExtensionRegistry().then((extensionRegistry: ExtensionDescriptionRegistry) => { extensionRegistry.onDidChange(_ => {

} get breakpoints(): vscode.Breakpoint[] { <del> this.startBreakpoints(); </del> const result: vscode.Breakpoint[] = []; this._breakpoints.forEach(bp => result.push(bp)); return result; } public addBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise<void> { <del> this.startBreakpoints(); </del> // filter only new breakpoints const breakpoints = breakpoints0.filter(bp => { const id = bp.id;

} public removeBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise<void> { <del> this.startBreakpoints(); </del> // remove from array const breakpoints = breakpoints0.filter(b => this._breakpoints.delete(b.id));

return undefined; } <del> private startBreakpoints() { if (!this._breakpointEventsActive) { this._breakpointEventsActive = true; this._debugServiceProxy.$startBreakpointEvents(); } } </del> private fireBreakpointChanges(added: vscode.Breakpoint[], removed: vscode.Breakpoint[], changed: vscode.Breakpoint[]) { if (added.length > 0 || removed.length > 0 || changed.length > 0) { this._onDidChangeBreakpoints.fire(Object.freeze({